当前位置: 动力学知识库 > 问答 > 编程问答 >

ruby on rails - 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
分享给朋友:
您可能感兴趣的文章:
随机阅读: