在has_many中过滤子对象:通过Rails 3中的关系
Posted
技术标签:
【中文标题】在has_many中过滤子对象:通过Rails 3中的关系【英文标题】:Filtering child objects in a has_many :through relationship in Rails 3 【发布时间】:2011-04-14 19:04:55 【问题描述】:您好,
我有一个应用程序,其中 Companies
和 Users
需要通过 CompanyMembership
模型相互归属,其中包含有关成员资格的额外信息(特别是,用户是否是公司的管理员,通过布尔值admin
)。代码的简单版本:
class CompanyMembership < ActiveRecord::Base
belongs_to :company
belongs_to :user
end
class Company < ActiveRecord::Base
has_many :company_memberships
has_many :users, :through => :company_memberships
end
class User < ActiveRecord::Base
has_many :company_memberships
has_many :companies, :through => :company_memberships
end
当然,这使得通过company.users.all
等获取公司的所有成员变得简单。但是,我正在尝试获取公司中作为该公司管理员的所有用户的列表(并且还测试用户是否是给定公司的管理员)。我的第一个解决方案是company.rb
:
def admins
company_memberships.where(:admin => true).collect do |membership|
membership.user
end
end
def is_admin?(user)
admins.include? user
end
虽然这行得通,但感觉效率低下(它迭代每个成员,每次都执行 SQL,对吗?或者 Relation 比这更聪明?),我不确定是否有更好的方法来解决这个问题(也许使用范围或 Rails 3 使用的新奇 Relation
对象?)。
我们将不胜感激任何关于最佳方法的建议(最好使用 Rails 3 最佳实践)!
【问题讨论】:
【参考方案1】:我相信我做错了,在company_memberships
上指定条件而不是users
,这正是我想要的(Users
的列表,而不是CompanyMemberships
的列表)。我认为我正在寻找的解决方案是:
users.where(:company_memberships => :admin => true)
生成以下 SQL(对于 ID 为 1 的公司):
SELECT "users".* FROM "users"
INNER JOIN "company_memberships"
ON "users".id = "company_memberships".user_id
WHERE (("company_memberships".company_id = 1))
AND ("company_memberships"."admin" = 't')
我还不确定是否需要它,但includes()
方法会在必要时执行预加载以减少 SQL 查询的数量:
Active Record 允许您指定 推进所有的协会 将被加载。这个有可能 通过指定
includes
方法Model.find
电话。包括, Active Record 确保所有 加载指定的关联 使用尽可能少的 查询。查询。 RoR Guides: ActiveRecord Querying
(我仍然愿意接受任何认为这不是解决此问题的最佳/最有效/正确方法的人的任何建议。)
【讨论】:
【参考方案2】:更简洁的方法是向您的公司模型添加关联,如下所示:
has_many :admins, :through => :company_memberships, :class_name => :user, :conditions => :admin => true
您可能需要深入研究rails doc 才能获得正确的语法。
您不应该需要 :include,除非您有其他与 :user 关联的类,您可能会在视图中引用这些类。
【讨论】:
:conditions
是 has_many
的有效选项吗?它没有出现在文档中。
@CeasarBautista 这个答案似乎已经过时了。请参阅标题“自定义查询”下的文档,它告诉您使用 ->
【参考方案3】:
这样的事情怎么样:
Company.find(:id).company_memberships.where(:admin => true).joins(:user)
【讨论】:
又近了一步!虽然这不是我想要的答案,但它引导我找到了它:)【参考方案4】:我偶然发现了这个答案,并相信现在有更好的方法使用has_many association scopes
(has_many documentation):
has_many :admins, -> where(admin: true) , through: :company_memberships, class_name: :user
has_many 关联的第二个参数可以是包含您的过滤器的 proc 或 lambda。
【讨论】:
这似乎是 2019 年的现代方式。您可以在->
的前面添加一个参数,这将是您正在使用的模型的实例,允许您参考它在where
【参考方案5】:
我制作了activerecord_where_assoc gem 来做到这一点。
有了它,无需加入或包含。进行即时加载仍然是您的选择,而不是义务。
users.where_assoc_exists(:company_memberships, admin: true, company_id: @company.id)
问题不清楚如何过滤公司,所以我自己添加了。如果没有这个,如果用户可以在多个公司工作,那么在一个公司担任管理员可能意味着在另一家公司被视为管理员。
gem 在 Rails 3 中不起作用,但我们已经 9 年后支持 Rails 4.1 及更高版本。
这里是introduction 和examples。在documentation 中阅读更多详细信息。
【讨论】:
以上是关于在has_many中过滤子对象:通过Rails 3中的关系的主要内容,如果未能解决你的问题,请参考以下文章