对相同模型之间的两种不同关系建模

Modelling two different relationships between the same models

两个模型组织和用户具有 1:many 关系,其中一个组织有多个用户(成员;一个用户也可以 与任何组织相关联):

class Organization < ActiveRecord::Base
  has_many :users, dependent: :destroy
  accepts_nested_attributes_for :users, :reject_if => :all_blank, :allow_destroy => true
  validates_associated :users
end

class User < ActiveRecord::Base
  belongs_to :organization, inverse_of: :users
end

一切正常,各种测试都通过了。
现在我为主持人功能添加了一个额外的关系,用户可以在其中拥有(多个)组织的主持人权限。因此,通过第三个模型建立 many:many 关系,我将其命名为 Moderator:

class Organization < ActiveRecord::Base
  has_many :users, dependent: :destroy
  accepts_nested_attributes_for :users, :reject_if => :all_blank, :allow_destroy => true
  has_many :moderators, class_name: "Moderator", foreign_key: "reviewee_id", dependent: :destroy
  has_many :users, through: :moderators, source: :reviewer
  validates_associated :users
end

class User < ActiveRecord::Base
  belongs_to :organization, inverse_of: :users
  has_many :moderators, class_name:  "Moderator",   foreign_key: "reviewer_id", dependent: :destroy
  has_many :organizations, through: :moderators, source: :reviewee
end

class Moderator < ActiveRecord::Base
  belongs_to :reviewee, class_name: "Organization"
  belongs_to :reviewer, class_name: "User"
end

我故意使用了 reviewerreviewee 名称。如果我只是在主持人模型中使用 user_idorganization_id,我认为这可能会把事情搞砸。因为如果您随后引用 @user.organization 则不会定义要使用的关系。它会使用用户和组织之间的 1:many 关系,还是 many:many through 关系...?通过对 many:many 直通关系使用不同的名称,@user.organization 应该指代 1:many 关系,而 @user.reviewee 例如应该指代 many:many 直通关系。

然而,在这个实现之后,突然各种测试都失败了。例如:我有一个表格可以为一个组织注册一个额外的用户。单击按钮会将 organization_id 传递给要为其创建其他用户的表单。现在突然这个 id 没有传递到表单,我得到所有 nil 错误,因为组织没有定义(即使 link 仍然是 url/member?organization_id=43)。我可以举出更多例子。

所以好像是因为新的关系发生了某种冲突。也许它无法理解何时使用 many:many 通过关系以及何时使用 1:many 关系,即使我使用了不同的审阅者和审阅者名称...... 我是否建模不正确或2个相同模型之间不可能有两种不同的关系吗?

如果我从组织模型中删除第二行 has_many :users,所有测试都会再次通过。所以问题似乎是我有两次这种关系。

处理这个问题的一个很好的通用模式称为资源范围角色。

一个用户可以有很多角色(父亲、母亲、主持人、hula-dancer 等),在某些情况下,角色的范围限定为特定资源。就像 father/mother 的范围是用户(child)或者版主可以是论坛的范围。

拥有 "system-level" 角色,如 super-admin 不属于资源的角色也很常见。

class User < ActiveRecord::Base
  has_many :roles
  scope :moderators, ->{ joins(:roles).where( roles: { name: 'moderator' } ) }
  belongs_to :organization
end

# columns: name:string, resource_id:int, resource_type:string, user_id:int
class Role < ActiveRecord::Base
  belongs_to :user
  belongs_to :resource, polymorphic: true
end

class Organization < ActiveRecord::Base
  has_many :roles, as: :resource
  has_many :users
  # This is just a relationship to users with a scope
  has_many :moderators, -> { moderators }, class_name: 'User'
end

所以要添加主持人,我们会这样做:

organization = Organization.find(1)
organization.roles.create(user: organization.users.find(1), name: 'moderator')

获取组织的所有版主:

moderators = Organization.find(1).moderators

这里最棒的是我们可以在任何资源上使用我们的角色 class - 不仅仅是一个组织。更好的是,有很棒的 gem 可以提供此功能,例如 Rolify.