在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 【问题描述】:

您好,

我有一个应用程序,其中 CompaniesUsers 需要通过 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 关联的类,您可能会在视图中引用这些类。

【讨论】:

:conditionshas_many 的有效选项吗?它没有出现在文档中。 @CeasarBautista 这个答案似乎已经过时了。请参阅标题“自定义查询”下的文档,它告诉您使用 -&gt;【参考方案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 年的现代方式。您可以在 -&gt; 的前面添加一个参数,这将是您正在使用的模型的实例,允许您参考它在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中的关系的主要内容,如果未能解决你的问题,请参考以下文章

Rails中如何通过对象同时保存多个has_many?

Rails范围过滤元素没有has_many关联元素

Rails 3 has_many 通过 3 个表

Rails has_many 关联计数子行

Has_many:通过 Rails 4 实例变量不起作用

Rails 成语避免在 has_many 中重复:通过