Techioz Blog

結合テーブルでフィルタリングできません

概要

Ruby on Rails で API を作成しており、Invoice、Package、Additional_charge という 3 つのテーブルがあります。

請求書には多くのパッケージが含まれており、パッケージには多くの追加料金が含まれています

こんな質問があります:

invoices = Invoice.includes(packages: :additional_charges)
                      .where(user_id: @current_user.id)
                      .order(created_at: :desc)

@invoices = invoices.paginate(page:, per_page:)

そして、データを返すために jbuilder を使用しています。 これは私の jbuilder ファイルです。

json.invoices @invoices do |invoice|
  json.(invoice, :id, :number, :total, :status, :created_at)
  json.packages invoice.packages do |package|
    json.(package, :tracking, :calculated_weight, :price)
    json.type package.shipping_method
    json.additional_charges package.additional_charges do |additional_charge|
      json.(additional_charge, :name, :amount)
    end
  end
end

しかし、問題は、Tracking という名前の Packages 列をフィルターしようとしたときに発生します。

コードを次のように変更してみました。

invoices = Invoice.includes(packages: :additional_charges)
                      .where(user_id: @current_user.id)

invoices = invoices.where('packages.tracking ILIKE ?', "%#{tracking}%") if tracking.present?

@invoices = invoices.order(created_at: :desc).paginate(page:, per_page:)

そして、私はこのエラーを受け取りました:

Packagesテーブルのtrackin列でフィルタリングできる方法はありますか?

結合も使用しようとしましたが、jbuilder は繰り返しデータを返します。

解決策

あなたの問題は、 include がどのように機能するかです。

include を使用すると、Rails はeager_load または preload を使用するかどうかを推測しようとします。この推論は、 where に提供されたクエリ条件の存在に基づいていますが、問題は、Rails が、関連付けられたテーブルが参照されているかどうかを判断するために文字列条件を解析しなくなったことです (長い間そうしていません)。

例えば:

SELECT invoices.* FROM invoices 
SELECT packages.* FROM packages WHERE packages.invoice_id IN (1,2,3)
SELECT invoices.id AS t0_r0, invoices.number AS t0_r1,...packages.id AS t1_r0, packages.tracking AS t1_r1,... FROM invoices LEFT OUTER JOIN packages ON packages.invoice_id = invoices.id 

あなたの場合、問題は実装がプリロードを使用しているため、パッケージテーブルがメインクエリに含まれていないことです。ただし、where の String 条件でそれを参照しているため、表示されているエラーが発生します。

最も簡単な解決策は、ActiveRecord::QueryMethods#references を使用することです。

References は Rails による推論を削除し、代わりにこのテーブルが参照されていることを Rails に明示的に伝えるため、コードは次のように変更されます。

invoices = invoices.references(packages: :additional_charges).where('packages.tracking ILIKE ?', "%#{tracking}%") if tracking.present?