简体   繁体   English

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

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

This is something I've come across a number of times now and I'd love to either figure out how to do what I'm wanting or build and submit a patch to Rails that does it. 这是我现在遇到过很多次了,我很想弄清楚如何做自己想要的事情,或者构建并提交一个补丁来向Rails提交。 Many times in my apps I'll have some models that look something like this: 在我的应用程序中,很多时候我会有一些看起来像这样的模型:

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

What I want to be able to do is access the relevant membership from a call to the association without doing additional queries. 我想要做的是从调用关联中访问相关成员资格,而无需执行其他查询。 For instance, I want to do something like this: 例如,我想做这样的事情:

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

Is there anything in Rails that allows this? Rails中有什么允许这样做的吗? Because the only methods I know to achieve the result I'm talking about are terribly ugly and inefficient, something like this: 因为我知道要达到我所谈论的结果的唯一方法非常丑陋且效率低下,所以这样的事情:

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

Does ActiveRecord have some arcane in-built facility to achieve this? ActiveRecord是否具有一些不可思议的内置工具来实现这一目标? It seems like it wouldn't be too hard, it's already having to fetch the join model in order to properly retrieve the association anyway so if this functionality doesn't exist it seems to me it should. 似乎并不难,它已经必须获取连接模型以正确地检索关联,因此,如果不存在此功能,我认为应该这样做。 I've run into this a number of times and am ready to roll up my sleeves and solve it for good. 我已经遇到过很多次了,并且准备好收起袖子并永久解决它。 What can I do? 我能做什么?

Update: The other pattern for this that I could use that basically gets what I want is something like this: 更新:我可以使用的另一种模式基本上可以得到我想要的东西,像这样:

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

But aesthetically I don't like this solution because I'm not actually interested in the memberships so much as I am the users and it feels wrong to be iterating over the join model instead of the association target. 但是从美学上讲,我不喜欢这种解决方案,因为我实际上对成员身份并不感兴趣,因为我是用户,并且对联接模型(而不是关联目标)进行迭代是错误的。 But this is better than the other way I pointed out above (thanks to Ian Yang of Intridea for reminding me of this one). 但这比我上面指出的另一种方法要好(感谢Intridea的Ian Yang提醒我这一点)。

If you just want to access some attributes in membership, there is an ugly trick 如果您只想访问成员资格中的某些属性,则有一个丑陋的窍门

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

It works because memberships is included in JOIN implicitly. 之所以有效,是因为成员资格隐式包含在JOIN中。

Update 更新资料

What's more, if you add another ugly trick in User 更重要的是,如果您在User添加了另一个丑陋的把戏

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

Now: 现在:

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

You can do it this way: 您可以这样操作:

Get all users within specific group memberships: 获取特定组成员资格内的所有用户:

Where group_array is an array of IDs for groups that you want the @user to be a member of. 其中group_array是您希望@user成为成员的组的ID数组。

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

Reverse it with: 用以下方法将其反转:

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

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

If the data you wish to access is an attribute on the join table, then includes is a pretty clean way to do it. 如果您希望访问的数据是联接表上的属性,则包括是一种非常干净的方法。

However, from your post it seems like you have a method on the membership that wants to do some intelligent work with the underlying data. 但是,从您的帖子中看来,您似乎有一种关于成员资格的方法,该方法希望对基础数据进行一些智能化处理。 Also, it seems like you want to do two things with one query: 另外,似乎您想对一个查询做两件事:

  1. Output user information (which is why you're iterating on the user object in the first place, if you just wanted memberships you'd just iterate on those). 输出用户信息(这就是为什么要首先迭代用户对象的原因,如果您只是想要成员身份,则只对那些对象进行迭代)。
  2. Output some intelligent and processed information for your membership object. 为您的成员对象输出一些智能的和已处理的信息。

As you've noticed, anything you do here feels weird because no matter where you put that code, it doesn't feel like the right model. 正如您所注意到的,您在这里所做的任何事情都感觉很奇怪,因为无论您将代码放在何处,都不会感觉像是正确的模型。

I usually identify this feeling as the need for another abstraction layer. 我通常将这种感觉识别为需要另一个抽象层。 Consider creating a new model called MembershipUsers (it's a terrible name, but you can think of a different one). 考虑创建一个名为MembershipUsers的新模型(这是一个糟糕的名字,但是您可以想到另一个模型)。

The following is my ad-hoc coding attempt that is untested but should give you an idea of the solution: 以下是我的临时编码尝试,未经测试,但可以使您了解解决方案:

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

By creating a class that represents the User and their Membership to a specified Group, you've created a context where the foo method feels appropriate. 通过创建一个表示User及其指定组成员身份的类,您创建了一个foo方法合适的上下文。 I'm guessing that foo didn't mean much without being in the context of a specific user, and that you references the associated user in the foo method. 我猜想,如果没有特定用户的上下文,foo就没有多大意义,并且您在foo方法中引用了关联的用户。

-Nick (@ngauthier) -尼克(@ngauthier)

EDIT: forgot to bring this full-circle: 编辑:忘记带这个完整的圆圈:

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.

相关问题 如何在has_many:through关联中访问联接模型的属性 - How do I access attributes of the join model in a has_many :through association 如何在has_many关联范围内访问模型的实例变量? - How can I access a model's instance variable within a has_many association scope? 我如何使用form_for更新关联的has_many:through关联 - How can I use form_for to update an association's has_many :through association 如何通过关系查询has_many,该关系在联接表中包含附加属性? - How can I query a has_many through relationship which contains an additional attribute in the join table? Rails 4 + Pundit:在has_many中加入模型授权:通过关联 - Rails 4 + Pundit : join model authorization in has_many :through association has_many的加入模型中的NameError:通过关联 - NameError in join model for has_many :through association 如何通过与其他属性关联使用has_many - How to use has_many through association with additional attributes 在Rails 3.1中,如何检查模型的has_many关联的任何实例是否已更改? - In Rails 3.1 how can I check if any instances of a model's has_many association have changed? has_many没有属性的模型:通过关联 - Model without attributes for has_many :through association 我是否需要has_many的联接表:通过关联? - Do I need a join table for a has_many :through association?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM