Reputation: 688
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
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