【问题标题】:Accessing the associated join model when iterating through a has_many :through association在遍历 has_many 时访问关联的连接模型:通过关联
【发布时间】:2016-08-23 21:13:58
【问题描述】:

我觉得这是一个非常基本的问题,但由于某种原因,我被它难住了(Rails 新手),似乎找不到答案(可能是我没有正确搜索)。

所以我有一个基本的 has_many :通过这样的关系:

class User < ApplicationRecord
  has_many :contacts, through :user_contacts

class Contact < ApplicationRecord
  has_many :users, through :user_contacts

在 users/show.html.erb 中,我正在遍历单个用户的联系人,例如:

<% @user.contacts.each do |c| %>
  <%= c.name %>
<% end %>

现在在每个循环中,我想访问与给定用户和联系人关联的 user_contact 连接模型,以显示 created_at 时间戳,该时间戳指示用户 联系关系的建立时间。

我知道我可以调用 UserContact.find 来通过 user_id 和 contact_id 在数据库中查找模型,但不知何故这感觉是多余的。如果我正确理解这是如何工作的(我完全有可能不理解),那么当我已经从数据库加载给定用户及其联系人时,应该已经加载了 user_contact 模型。我只是不知道如何正确访问正确的模型。有人可以帮助使用正确的语法吗?

【问题讨论】:

    标签: ruby-on-rails activerecord


    【解决方案1】:

    实际上连接模型还没有被加载:ActiveRecord 使用through 规范来构建它的SQL JOIN 语句来查询正确的Contact 记录,但实际上只会实例化这些记录。

    假设你有一个UserContact 模型,你可以这样做:

    @user.user_contacts.includes(:contact).find_each do |uc|
        # now you can access both join model and contact without additional queries to the DB
    end
    

    如果你想保持可读性而不用uc.contact.something 弄乱你的代码,你可以在UserContact 模型中设置委托,将一些属性分别委托给contactuser。比如这个

    class UserContact < ActiveRecord::Base
      belongs_to :user
      belongs_to :contact
      delegate :name, to: :contact, prefix: true
    end
    

    可以让你写

    uc.contact_name
    

    【讨论】:

      【解决方案2】:

      首先,has_many :things, through: :other_things 子句要查找other_things 关系以找到:things

      可以将其视为一种方法调用,其中内置了魔法以使其在 SQL 查询中表现出色。因此,通过使用 through 子句,您或多或少会执行以下操作:

      def contacts
        user_contacts.map { |user_contact| user_contact.contacts }.flatten
      end
      

      user_contacts 的上下文完全丢失了。

      因为看起来user_contacts 是一对一的连接。这样做会更容易:

      <% @user.user_contacts.each do |user_contact| %>
        <%= user_contact.contact.name %>
      <% end %>
      

      此外,由于您是 Rails 新手,值得一提的是,要在没有 N+1 查询的情况下加载这些记录,您可以在控制器中执行以下操作:

      @user = User.includes(user_contacts: [:contacts]).find(params[:id])
      

      【讨论】:

      • 谢谢,搞定了。对于将来偶然发现此问题的任何人的一个澄清,包含的正确语法是在 .find 之前:@user = User.includes(user_contacts: [:contact]).find(params[:id])
      • 啊,你是对的。 find 不返回 ActiveRecord::Relation。谢谢,我会解决的。
      【解决方案3】:

      这样使用.joins.select

      @contacts = current_user.contacts.joins(user_contacts: :users).select('contacts.*, user_contacts.user_contact_attribute_name as user_contact_attribute_name')

      现在,在@contacts.each do |contact| 循环中,您可以调用contact.user_contact_attribute_name

      这看起来很奇怪,因为contact 没有user_contact_attribute_name,只有UserContact 有,但是查询的.select 部分将让您在每个contact 实例上神奇地使用它。

      contacts.* 部分告诉查询使所有contact 的属性也可用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-02
        • 1970-01-01
        相关资源
        最近更新 更多