问候,
我有一个应用程序,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,对吗?或者是比这更聪明的关系吗?),我不确定是否有更好的方法来解决这个问题(可能使用范围或Rails 3使用的奇特的新Relation
对象?)。
任何关于最佳处理方式的建议(最好使用Rails 3最佳实践)将不胜感激!
我相信我这是错误的方式,指定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
方法来实现。使用includes,Active Record可确保使用尽可能少的queries.queries加载所有指定的关联。 RoR Guides: ActiveRecord Querying
(对于那些认为这不是最好/最有效/最正确的方法的人,我仍然愿意接受任何建议。)
更简洁的方法是在公司模型中添加关联,如下所示:
has_many :admins, :through => :company_memberships, :class_name => :user, :conditions => {:admin => true}
您可能需要深入了解rails doc以获得正确的语法。
您不应该:include,除非您有其他类与您关联:您可能在视图中引用的用户。
这样的事情怎么样:
Company.find(:id).company_memberships.where(:admin => true).joins(:user)
我偶然发现了这个答案,并相信现在使用has_many association scopes
(has_many documentation)有一个更好的方法:
has_many :admins, -> { where(admin: true) }, through: :company_memberships, class_name: :user
has_many关联的第二个参数可以是包含过滤器的proc或lambda。