如何在没有其他查询的情况下访问has_many:通过关联对象的相关联接模型?

这是我现在遇到过很多次的事情,我很想知道如何做我想要的或构建并向Rails提交补丁。 很多时候在我的应用程序中,我会有一些看起来像这样的模型:

class User < ActiveRecord::Base has_many :memberships has_many :groups, through: :memberships end class Membership belongs_to :user belongs_to :group def foo # something that I want to know end end class Group has_many :memberships has_many :users, through: :memberships end 

我希望能够做的是通过调用该关联来访问相关的成员资格,而无需进行其他查询。 例如,我想做这样的事情:

 @group = Group.first @group.users.each do |user| membership = user.membership # this would be the membership for user in @group end 

Rails中有什么允许这个吗? 因为我所知道的唯一方法来实现我所说的结果非常难看且效率低下,如下所示:

 @group.users.each do |user| membership = Membership.where(group_id: @group.id, user_id:user.id).first end 

ActiveRecord是否有一些神秘的内置工具来实现这一目标? 看起来它不会太难,它已经必须获取连接模型才能正确地检索关联,所以如果这个function不存在,我觉得它应该。 我已经多次遇到这种情况,我已准备好卷起袖子并妥善解决。 我能做什么?

更新:我可以使用的其他模式基本上得到我想要的是这样的:

 @group.memberships.includes(:user).each do |membership| user = membership.user end 

但从美学角度来说,我不喜欢这种解决方案,因为我实际上对成员资格并不感兴趣,因为我是用户,并且迭代连接模型而不是关联目标是错误的。 但这比我上面指出的其他方式要好(感谢Intridea的Ian Yang提醒我这个)。

如果你只想访问会员资格中的一些属性,那就有一个丑陋的伎俩

 group.users.select('*, memberships.some_attr as membership_some_attr') 

它的工作原理是因为成员资格隐式包含在JOIN中。

更新

更重要的是,如果你在User添加另一个丑陋的技巧

 class User < ActiveRecord::Base has_many :memberships has_many :groups, through: :memberships belongs_to :membership #trick, cheat that we have membership_id end 

现在:

 group.users.select('*, memeberships.id as membership_id').includes(:membership).each do |user| membership = user.membership end 

如果您要访问的数据是连接表上的属性,那么包含是一种非常干净的方法。

但是,从您的post看,您似乎有一个成员资格的方法,希望对基础数据进行一些智能工作。 此外,您似乎想要使用一个查询执行两项操作:

  1. 输出用户信息(这就是您首先在用户对象上进行迭代的原因,如果您只是想要成员资格,那么您只需要对这些成员进行迭代)。
  2. 为您的会员对象输出一些智能和处理过的信息。

正如你所注意到的,你在这里做的任何事情都会让人觉得奇怪,因为无论你把代码放在哪里,它都不像是正确的模型。

我通常认为这种感觉需要另一个抽象层。 考虑创建一个名为MembershipUsers的新模型(这是一个可怕的名字,但你可以想到一个不同的名称)。

以下是我未经测试的特殊编码尝试,但应该让您了解解决方案:

 class MembershipUser < User def self.for_group(group) select('memberships.*, users.*'). joins('join memberships on memberships.user_id = users.id'). where('memberships.group_id = ?', group.id) end def foo # now you have access to the user attributes and membership attributes # and you are free to use both sets of data for your processing end end 

通过创建一个表示用户及其成员身份到指定组的类,您已经创建了一个foo方法感觉合适的上下文。 我猜测foo在没有特定用户的上下文中并没有多大意义,并且你在foo方法中引用了相关的用户。

- 尼克(@ngauthier)

编辑:忘了带这个圆圈:

 class Group def membership_users MembershipUser.for_group(self) end end # then iterate group.membership_users.each do |membership_user| membership_user.user_name # a pretend method on user model membership_user.foo # the foo method that's only on MembershipUser end 
  users = Group.first.users.select("*, memberships.data as memberships_data, users.*") 

这将使您可以访问所有内容

你可以这样做:

获取特定组成员身份中的所有用户:

其中group_array是您希望@user成为其成员的组的ID数组。

 @user = User.all( include: :groups, conditions: ["memberships.group_id in (?)", group_array] ).first 

反转它:

 @group = Group.all( include: :users, conditions: ["memberships.user_id in (?)", user_array] ).first