0

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.

Community
  • 1
  • 1
StorymasterQ
  • 264
  • 2
  • 17

2 Answers2

0
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
Olivier
  • 696
  • 4
  • 12
  • That's the linked question's solution, but I can't make it work. How do I bring the new table into the association? – StorymasterQ Mar 23 '16 at 02:24
  • What about this update ? Not sure that's what you're trying to accomplish but you maybe should take a look on that kind of structure. – Olivier Mar 23 '16 at 02:54
  • Hm, so I should/must add a `manager_id` column to the `users` table? – StorymasterQ Mar 23 '16 at 03:02
  • It depends what you're trying to do but if you want to link a user to another one which will be his parent with the same class, using a foreign_key like this is a good way to do that. – Olivier Mar 23 '16 at 12:26
0

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.

max
  • 96,212
  • 14
  • 104
  • 165
  • I'm not supposed to edit the `users` table, but if the other option is adding yet another gem, maybe a new column isn't such a bad idea. – StorymasterQ Mar 23 '16 at 03:31
  • I think I'm going with the `manager_id` column. How do I get the newly inserted manager's name for the error message? EDIT: Nevermind, I figured out `manager.name`. – StorymasterQ Mar 23 '16 at 06:24
  • "adding yet another gem" is not always a bad thing. Don't reinvent the wheel because of fear of technical dept or not invented here syndrome. – max Mar 23 '16 at 09:33
  • This especially applies to authentication and authorization. Thats why people get hacked. – max Mar 23 '16 at 09:45