I am creating an API in ruby on rails and I have 3 tables Invoice, Package and Additional_charge.
Invoice has many Packages and Packages has many Additional_charges
I have this query:
invoices = Invoice.includes(packages: :additional_charges)
.where(user_id: @current_user.id)
.order(created_at: :desc)
@invoices = invoices.paginate(page:, per_page:)
And I am using jbuilder to return the data.
This is my jbuilder file.
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
But the problems is when I tried to filter for a Packages column named Tracking.
I tried modifying the code like this:
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:)
And I got this error:
ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: missing FROM-clause entry for table "packages" LINE 1: ...M "invoices" WHERE "invoices"."user_id" = $1 AND (packages.t...
Is there a way so I can filter by trackin column from Packages table?
I tried using joins too but the jbuilder returns me repeated data.
Your issue is how
includesworks.When using
includesRails tries to infer whether or not to useeager_loadorpreload. This inference is based on the existence of query conditions provided towhere, the problem is Rails no longer (hasn't for a very long time) tries to parse string conditions to determine if an associated table is referenced.For example:
This will use
preload-invoices = Invoice.includes(packages: :additional_charges)because neitherpackagesnoradditional_chargesare referencedThis will also use
preload-invoices = Invoice.includes(packages: :additional_charges).where('packages.tracking ILIKE ?', "%#{tracking}%")because Rails cannot determine thatpackagesis referenced in the String conditionpreloadwill execute 2 queries the first to collect the objects from the primary table (invoices) and another query to collect all of the associated packages e.g. (in simplified form removing the additional_charges portion for demonstration purposes)This will use
eager_load-invoices = Invoice.includes(packages: :additional_charges).where(packages: {tracking: '123'})because rails can infer the need to join thepackagestable based on the condition.eager_loadwill left join the "included" tables and collect all of the data at the same time in 1 query e.g.In your case the issue is that your implementation uses
preloadand therefor the packages table is not included in the main query; however, you are referencing it in your String condition in where, which causes the error you are seeing.The simplest resolution would be to use ActiveRecord::QueryMethods#references
referencesremoves the inference by Rails and instead explicitly tells Rails that this table is referenced, so your code would change to