繁体   English   中英

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

[英]How can I access a has_many :through association object's relevant join model without additional queries?

这是我现在遇到过很多次了,我很想弄清楚如何做自己想要的事情,或者构建并提交一个补丁来向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提醒我这一点)。

如果您只想访问成员资格中的某些属性,则有一个丑陋的窍门

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

您可以这样操作:

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

其中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
   users = Group.first.users.select("*, memberships.data as memberships_data, users.*")

这将使您能够访问所有内容

如果您希望访问的数据是联接表上的属性,则包括是一种非常干净的方法。

但是,从您的帖子中看来,您似乎有一种关于成员资格的方法,该方法希望对基础数据进行一些智能化处理。 另外,似乎您想对一个查询做两件事:

  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

通过创建一个表示User及其指定组成员身份的类,您创建了一个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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM