在Rails 3中的has_many:through关系中过滤子对象

时间:2010-09-20 16:52:52

标签: activerecord ruby-on-rails-3 has-many-through

问候,

我有一个应用程序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,对吗?或者是比这更聪明的关系?),我不确定是否有更好的方法来解决这个问题(可能使用Rails 3使用的范围或花哨的新Relation对象?)。

任何关于最佳处理方式的建议(最好使用Rails 3最佳实践)都将不胜感激!

5 个答案:

答案 0 :(得分:17)

我相信我的方法是错误的,在company_memberships而不是users上指定条件,这是我真正想要的(Users列表,而不是CompanyMemberships列表users.where(:company_memberships => {:admin => true}) )。我认为我正在寻找的解决方案是:

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')

生成以下SQL(对于ID为1的公司):

includes()

我不确定我是否需要它,但includes方法将执行急切加载以在必要时降低SQL查询的数量:

  

Active Record允许您指定   推进所有的协会   将要加载。这个有可能   通过指定Model.find方法   {{1}}来电。随着包括,   Active Record确保所有的   已加载指定的关联   使用最小可能的数量   queries.queries。 RoR Guides: ActiveRecord Querying

(对于任何认为这不是最好/最有效/最正确的方法的人,我仍然愿意接受任何建议。)

答案 1 :(得分:7)

更简洁的方法是在公司模型中添加关联,如下所示:

has_many :admins, :through => :company_memberships, :class_name => :user, :conditions => {:admin => true}

您可能需要深入了解rails doc以获得正确的语法。

您不应该:include,除非您有其他与您关联的类:您可以在视图中引用该用户。

答案 2 :(得分:3)

这样的事情怎么样:

Company.find(:id).company_memberships.where(:admin => true).joins(:user)

答案 3 :(得分:0)

我偶然发现了这个答案,并相信如今有一种使用has_many association scopeshas_many documentation)的更好的方法:

has_many :admins, -> { where(admin: true) }, through: :company_memberships, class_name: :user

has_many关联的第二个参数可以是包含您的过滤器的proc或lambda。

答案 4 :(得分:0)

为此,我制作了activerecord_where_assoc宝石。

有了它,就不需要加入或包含。渴望加载仍然是您的选择,而不是义务。

users.where_assoc_exists(:company_memberships, admin: true, company_id: @company.id)

有关如何过滤公司的问题尚不清楚,所以我自己添加了它。没有这个,如果一个用户可以在多个公司中,则在一个公司中被管理员管理可能意味着在另一个公司中被视为管理员。

gem在Rails 3中不起作用,但是我们已经9年了,并且支持Rails 4.1及更高版本。

这里是introductionexamples。在documentation中了解更多详细信息。