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

时间:2011-06-15 22:31:36

标签: ruby-on-rails activerecord

这是我现在已经遇到过很多次的事情,我很想知道如何做我想要的事情或构建并向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是否有一些神秘的内置工具来实现这一目标?看起来它不会太难,它已经必须获取连接模型才能正确检索关联,所以如果这个功能不存在,我认为它应该。我已经多次遇到这种情况,并准备好卷起袖子并妥善解决。我该怎么办?

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

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

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

4 个答案:

答案 0 :(得分:2)

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

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

答案 1 :(得分:0)

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

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

  1. 输出用户信息(这就是您首先在用户对象上进行迭代的原因,如果您只是想要成员资格,那么您只需要对其进行迭代)。
  2. 为您的会员对象输出一些智能和处理过的信息。
  3. 正如你所注意到的,你在这里做的任何事情都会让人觉得奇怪,因为无论你把代码放在哪里,它都感觉不像是正确的模型。

    我通常认为这种感觉需要另一个抽象层。考虑创建一个名为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方法中引用了相关的用户。

    -Nick(@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
    

答案 2 :(得分:0)

你可以这样做:

获取特定群组成员资格中的所有用户:

其中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

答案 3 :(得分:0)

   users = Group.first.users.select("*, memberships.data as memberships_data, users.*")

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