Rails Mountable Engine with CanCan - ruby-on-rails

I'm working on a blog engine and I wan't to setup cancan. I found some ways to do it:
forem doest the thing by adding an admin field in the host user model, but having multiple roles would require many fields, and that is definitely not the way to do it
I saw some exames like this but to do it I would need to have my host app user model having has_many relations to my engine models, like this
class User < ActiveRecord::Base
attr_accessible :name
has_many Blogcms::RoleUser
has_many Blogcms::Role, :through => Blogcms::RoleUser
end
I don't really know if this is the right way to do it, but this wont work because beeing a isolated engine the engine models will be invisible
has someone tried this?
sorry for my english
EDIT
I found a way around, not having to search through the User model and just setting up the relations in my Role model
module Blogcms
class Role < ActiveRecord::Base
has_many :role_user
has_many :user, :class_name => Blogcms.user_class, :through => :role_user
attr_accessible :name
# Return all the roles for a user
def self.roles_for_user(user)
joins(:user).where('users.id' => user.id)
end
end
end
like that my roles_for_user methods returns all the roles for a user

I found a way around, not having to search through the User model and just setting up the relations in my Role model
module Blogcms
class Role < ActiveRecord::Base
has_many :role_user
has_many :user, :class_name => Blogcms.user_class, :through => :role_user
attr_accessible :name
# Return all the roles for a user
def self.roles_for_user(user)
joins(:user).where('users.id' => user.id)
end
end
end
like that my roles_for_user methods returns all the roles for a user

Related

ActiveRecord has_many and belongs_to on the same model

I've looked into this SO question but I still have trouble wrapping my head around the concept.
I have a similar setup with the linked SO question, in that I have a User class that contains both Employees and Managers. I also have another model for Role (holding the role names) and UserRole (holding which user has which role).
My requirements state that an Employee (a User whose Role is User) can only have one Manager (a User whose Role is Manager). Now, this Managering concept is an addition to the current system, and I'm not supposed to change the users table, so I'm making a new table with their own MVC.
But now I find it hard to use has_many and belongs_to like the linked question. How do I use the new model? I tried using :through but it doesn't work (for some reason).
Am I doing it wrong? Should I just add a manager_id column to users and work the solution in the linked question into my problem? Also, how do I ensure that only a User whose Role is Manager can be set as a Manager?
Note: I have to say that I'm relatively new to Rails and ActiveRecord, and even Ruby in general.
Note 2: I'm using Rails 4.2.0 if it's relevant.
Setting up a many to many system with roles is pretty straight forward:
class User
has_many :user_roles
has_many :roles, through: :user_roles
def has_role?(role)
roles.where(name: role).any?
end
end
class Role
has_many :user_roles
has_many :users, through: :user_roles
end
class UserRole
belongs_to :role
belongs_to :user
validates_uniqueness_of :role, :user
end
Just make sure you create a unique index on UserRole for role and user:
add_index :user_roles, [:role_id, :user_id], unique: true
The simplest and performant way to implement the manager requirement would be to add a
mananger_id column to users and setup a self-referencing one to many relationship:
class User
has_many :user_roles
has_many :roles, through: :user_roles
belongs_to :manager, class_name: 'User'
has_many :subordinates, foreign_key: :manager_id, class_name: 'User'
validate :authorize_manager!
def has_role?(role)
roles.where(name: role).any?
end
private
def authorize_manager!
if manager.present?
errors.add(:manager, "does not have manager role") unless manager.has_role?("manager")
end
end
end
Another way to do this would be to use resource scoped roles.
The best part is that you don't have build it yourself. There is a excellent gem created by the community called Rolify which sets you up with such as system.
Its also quite a bit more flexible than the former system, once you get a hang of it you can add roles to any kind of resource in your domain.
class User < ActiveRecord::Base
rolify
resourcify
end
---
the_boss = User.find(1)
bob = User.find_by(name: 'Bob')
# creating roles
the_boss.add_role(:manager) # add a global role
the_boss.add_role(:manager, bob) # add a role scoped to a user instance
# querying roles
bob.has_role?(:manager) # => false
the_boss.has_role?(:manager) # => true
the_boss.has_role?(:manager, bob) # => true
the_boss.has_role?(:manager, User.create) # => false
If you go with Rolify compliment it with an authorization library such as Pundit or CanCanCan to enforce the rules.
class User < ActiveRecord::Base
belongs_to :manager, class_name: 'User'
has_many :employees, foreign_key: :manager_id, class_name: 'User'
end
Does it helps ?
Update:
class User < ActiveRecord::Base
has_one :role, through: :user_role
belongs_to :manager, -> { where(role: {name: 'manager'}), class_name: 'User'
has_many :employees, -> { where(role: {name: 'employee'}), foreign_key: :manager_id, class_name: 'User'
end

Aliasing model names in Rails

I'm working on the design of an application in which there will be users with two (not necessarily distinct) roles, teachers and students. So, I'm thinking I'd have one User class with associated roles.
To make the code (hopefully) more readable, I'd like to refer to instances of the User class by an alias where the role is required. For example if the user must have the role of student I'd like to do something like this:
class Section < ActiveRecord::Base
alias :user :student
alias :user :teacher
belongs_to :teacher
has_many :students
end
Is there a way to do something like this?
You can do
class Section < ActiveRecord::Base
belongs_to :teacher, :class_name => "Section", :foreign_key => "teacher_section_id"
has_many :students, :class_name => "Section", :foreign_key => "teacher_section_id"
end
Answer copied from:
rails model has_many of itself
EDIT:
How are you going to differentiate a Teacher from a user?
I think you should make a column(section_type) in Section table for that.
And then you can create scopes like
class Section < ActiveRecord::Base
belongs_to :teacher,-> {where("section_type == 'teacher'")} ,:class_name => "Section", :foreign_key => "teacher_section_id"
has_many :students, -> {where("section_type == 'students'")},:class_name => "Section", :foreign_key => "teacher_section_id"
end
If you are dealing with roles you might want to have a look at the Rolify gem. It's very simple and has documentation that will show you some common approaches to dealing with roles. The methods above seem a little unconventional, which may suit your use case but I have had great luck with Rolify for fairly large apps.
https://github.com/RolifyCommunity/rolify
If I understand correctly what you're trying to do, you would subclass User as follows:
class Teacher < User
end
class Student < User
end
and use the belongs_to and has_many associations exactly as you have them in Section.

ActiveRecord won't build the right class using STI

I'm using single table inheritance in my application and running into problems building inherited users from an ancestor. For instance, with the following setup:
class School < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
attr_accessible :type #etc...
belongs_to :school
end
Class Instructor < User
attr_accessible :terms_of_service
validates :terms_of_service, :acceptance => true
end
Class Student < User
end
How can I build either a instructor or student record from an instance of School? Attempting something like School.first.instructors.build(....) gives me a new User instance only and I won't have access to instructor specific fields such as terms_of_service causing errors later down the rode when generating instructor-specific forms, building from console will give me an mass-assignment error (as it's trying to create a User record rather than an Instructor record as specified). I gave the example of School, but there are a few other associations that I would like to inherit from the User table so I don't have to repeat code or fields in the database. Am I having this problem because associations can not be shared in an STI setup?
You should specify instructors explicitly
class School < ActiveRecord::Base
has_many :users
has_many :instructors,:class_name => 'Instructor', :foreign_key => 'user_id'
end
And what else:
class School < ActiveRecord::Base
has_many :users
has_many :instructors
end
class Instructor < User
attr_accessible :terms_of_service # let it be at the first place. :)
validates :terms_of_service, :acceptance => true
end
OK it seems part of the problem stemmed from having the old users association inside of my School model. Removing that and adding the associations for students and instructors individually worked.
Updated School.rb:
class School < ActiveRecord::Base
#removed:
#has_many :users this line was causing problems
#added
has_many :instructors
has_many :students
end

Mongoid Relations (Custom accessors)

I am using Mongoid in my Rails app. I have a User model and I also have a Role model (think Admin, guest etc).
I want to setup my Mongoid associations such that the following code will be possible:
u = User.first
u.invited_roles # returns all roles a user has been assigned
r = Role.new
r.invitee = user # user id should be stored in the Role
r.save!
Currently, my models are set up as so:
class User
include Mongoid::Document
has_many :invited_roles, :class_name => 'Role'
end
class Role
include Mongoid::Document
has_one :invitee, :class_name => 'User'
end
Currently, when I do Role.new from the Rails console, I get an object that does not appear to have a field to store the invitee User. How do I fix this? I have seen people using inverse_of but I can't really find any tutorials or documentation on this procedure.
Thanks for the help.
Well, thinking about this --- a User will be invited to have many roles, but that role will only have one user. Now, this is not "have one" in the sense of has_one. Instead, it's defining a child relationship.
So, change the has_one to belongs_to and you're golden.
class User
include Mongoid::Document
has_many :invited_roles, :class_name => 'Role'
end
class Role
include Mongoid::Document
belongs_to :invitee, :class_name => 'User'
end

Is there a way to have three way habtm associations in rails / activerecord?

Often three (or more) way associations are needed for habtm associations. For instance a permission model with roles.
A particular area of functionality has many users which can access many areas.
The permissions on the area are setup via roles (habtm)
The user/roles association is also habtm
The permissions (read, write, delete, etc) are habtm towards roles.
How would that be best done with rails/activerecord?
I'm not sure if you are just using Role based user permissions as an example, or if that is actually your goal.
Nested habtm relationships are easy in Rails, though I would highly recommend Nested has_many :through, just set them up as you would imagine:
class Permission < ActiveRecord::Base
end
#this table must have permission_id and role_id
class PermissionAssignment < ActiveRecord::Base
belongs_to :permission
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :users, :through => :role_assignments
has_many :permissions, :through => :permission_assignments
end
#this table must have user_id and role_id
class RoleAssignment < ActiveRecord::Base
belongs_to :role
belongs_to :user
end
class User < ActiveRecord::Base
has_many :roles, :through => :role_assignments
end
This question about rails and RBAC (role-based authentication control) has a bunch of useful samples that provide the answer to this question and also sample implementations of your example.

Resources