对相同模型之间的两种不同关系建模
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
我故意使用了 reviewer
和 reviewee
名称。如果我只是在主持人模型中使用 user_id
和 organization_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.
两个模型组织和用户具有 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
我故意使用了 reviewer
和 reviewee
名称。如果我只是在主持人模型中使用 user_id
和 organization_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.