pinkfloyd90
pinkfloyd90

Reputation: 688

Eager loading the last record of an associated model

I have a model Invoice which has_many Payments and a model Payment that belongs_to Invoice.

Invoice.rb

class Invoice < ApplicationRecord
  has_many :payments, inverse_of: :invoice
end

Payment.rb

class Payment < ApplicationRecord
  belongs_to :invoice
end

We export Invoice data monthly in batches, and we need each Invoice's last Payment.

In our view we are currently doing Invoice.payments.last once for each Invoice that we are exporting, and I was asked to prevent N+1 queries.

I've tried adding the following associaton to the Invoice model:

has_one     :last_approved_payment,
              -> { invoice.payments.approved.last },
              class_name: "Payment", inverse_of: :invoice

But I'm getting the following error:

Invoice.first.last_approved_payment
  Invoice Load (0.4ms)  SELECT "invoices"."id", "invoices"."user_id", "invoices"."invoiceable_type", "invoices"."invoiceable_id", "invoices"."status", "invoices"."number", "invoices"."period", "invoices"."external_id", "invoices"."external_status", "invoices"."created_at", "invoices"."updated_at", "invoices"."due_date", "invoices"."metadata", "invoices"."rejection_code", "invoices"."retry_attempt", "invoices"."next_retry_date", "invoices"."last_retry_date", "invoices"."expire_date", "invoices"."external_service", "invoices"."external_type", "invoices"."external_info", "invoices"."account_id", "invoices"."temp_due_date", "invoices"."next_attempt_date", "invoices"."last_attempt_date", "invoices"."processing_stage", "invoices"."amount_cents", "invoices"."amount_currency", "invoices"."desist_retrying" FROM "invoices" ORDER BY "invoices"."id" ASC LIMIT $1  [["LIMIT", 1]]
  Payment Load (0.3ms)  SELECT "payments"."id", "payments"."operation_id", "payments"."status", "payments"."status_detail", "payments"."payment_type", "payments"."external_reference", "payments"."payer_email", "payments"."date_created", "payments"."date_approved", "payments"."money_release_date", "payments"."comment", "payments"."created_at", "payments"."updated_at", "payments"."user_id", "payments"."operation_type", "payments"."reason", "payments"."reasonable_type", "payments"."reasonable_id", "payments"."invoice_id", "payments"."metadata", "payments"."account_id", "payments"."amount_cents", "payments"."amount_currency", "payments"."payer_id_ciphertext" FROM "payments" LIMIT $1  [["LIMIT", 11]]
  Payment Load (0.4ms)  SELECT "payments"."id", "payments"."operation_id", "payments"."status", "payments"."status_detail", "payments"."payment_type", "payments"."external_reference", "payments"."payer_email", "payments"."date_created", "payments"."date_approved", "payments"."money_release_date", "payments"."comment", "payments"."created_at", "payments"."updated_at", "payments"."user_id", "payments"."operation_type", "payments"."reason", "payments"."reasonable_type", "payments"."reasonable_id", "payments"."invoice_id", "payments"."metadata", "payments"."account_id", "payments"."amount_cents", "payments"."amount_currency", "payments"."payer_id_ciphertext" FROM "payments" LIMIT $1  [["LIMIT", 11]]
**NameError: undefined local variable or method `invoice' for #<Payment::ActiveRecord_Relation:0x000055b544ccf138>
from /home/vm/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/activerecord-6.0.3.6/lib/active_record/relation/delegation.rb:109:in `method_missing'**

Upvotes: 0

Views: 83

Answers (1)

Sebasti&#225;n Palma
Sebasti&#225;n Palma

Reputation: 33460

You don't need to explicitly add a default scope to the model relationship, what you can do is just to preload the payments table when retrieving your invoices;

Invoice.includes(:payments)

Although it'd be better IMO if you just select what you really need from both models, you can achieve that with a subquery using select:

Invoice.select(
  :id,
  "(SELECT p.id,
    FROM payments p
    WHERE p.invoice_id = invoices.id
    ORDER BY created_at DESC
    LIMIT 1) AS payment_id"
)

Upvotes: 1

Related Questions