【问题标题】:ActiveRecord has_many and belongs_to on the same modelActiveRecord has_many 和 belongs_to 在同一个模型上
【发布时间】:2016-03-23 11:13:23
【问题描述】:

我已经研究了这个SO question,但我仍然无法理解这个概念。

我有一个与链接的 SO 问题类似的设置,因为我有一个包含员工和经理的 User 类。我还有另一个模型Role(保存角色名称)和UserRole(保存哪个用户具有哪个角色)。

我的要求是一个员工(一个User,其RoleUser)只能有一个经理(一个User,其RoleManager)。现在,这个管理概念是对当前系统的补充,我不应该更改 users 表,所以我正在使用他们自己的 MVC 制作一个新表。

但现在我发现很难像链接的问题一样使用has_manybelongs_to。如何使用新模型?我尝试使用:through,但它不起作用(出于某种原因)。

我做错了吗?我是否应该将manager_id 列添加到users 并将链接问题中的解决方案应用于我的问题?另外,如何确保只有RoleManagerUser可以设置为Manager?

注意:我不得不说我对 Rails 和 ActiveRecord,甚至一般的 Ruby 都比较陌生。

注意 2:如果相关,我使用的是 Rails 4.2.0。

【问题讨论】:

    标签: ruby-on-rails activerecord


    【解决方案1】:

    使用角色设置多对多系统非常简单:

    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
    

    只需确保在 UserRole 上为角色和用户创建唯一索引:

    add_index :user_roles, [:role_id, :user_id], unique: true
    

    实现经理要求的最简单和高效的方法是添加一个 mananger_id 列到 users 并设置一个自引用的一对多关系:

    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
    

    另一种方法是使用资源范围角色。 最好的部分是您没有自己构建它。社区创建了一个名为Rolify 的优秀gem,它可以为您设置诸如系统之类的东西。

    它也比以前的系统更灵活,一旦你掌握了它,你就可以为你域中的任何类型的资源添加角色。

    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
    

    如果您使用 Rolify,请使用 Pundit 或 CanCanCan 等授权库来补充它以执行规则。

    【讨论】:

    • 我不应该编辑 users 表,但如果另一个选项是添加另一个 gem,那么新列也许不是一个坏主意。
    • 我想我会选择manager_id 列。如何获取新插入的经理的错误消息名称?编辑:没关系,我发现manager.name
    • “添加另一个宝石”并不总是一件坏事。不要因为害怕技术部门或者不是这里发明综合症而重新发明轮子。
    • 这尤其适用于身份验证和授权。这就是人们被黑客入侵的原因。
    【解决方案2】:
    class User < ActiveRecord::Base
      belongs_to :manager, class_name: 'User'
      has_many :employees, foreign_key: :manager_id, class_name: 'User'
    end
    

    有帮助吗?

    更新:

    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
    

    【讨论】:

    • 这是链接问题的解决方案,但我无法使其工作。如何将新表带入关联?
    • 这次更新怎么样?不确定这就是你想要完成的,但你也许应该看看这种结构。
    • 嗯,所以我应该/必须在users 表中添加一个manager_id 列?
    • 这取决于你想要做什么,但如果你想将一个用户链接到另一个将是他的父级的用户,那么使用这样的 foreign_key 是一个很好的方法.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多