I have a "Loan" model in Rails I'm trying to build. There's a corresponding "Payment" model. The balance on the loan is the original amount of the loan minus the sum of all the payments. Calculating the balance is easy, but I'm trying to calculate the balance on lots of loans while avoiding an N+1 query, while at the same time making the "balance" a property of the "Loan" model.
When I call the index method of the Loans controller, I can run a custom select query, which allows me to return a "balance" property through a straight SQL query.
class LoansController < ApplicationController
def index
@loans = Loan
.joins("LEFT JOIN payments on payments.loan_id = loan.id")
.group("loans.id")
.select("loans.*, loans.amount - SUM(payments.amount) as balance")
end
def index_002
@loans = Loan.includes(:payments)
end
def index_003
@loans = Loan.includes(:payments)
end
end
class Loan < ActiveRecord::Base
has_many :payments
def balance=(value)
# I'd like balance to load automatically in the Loan model.
raise NotImplementedError.new("Balance of a loan cannot be set directly.")
end
def balance_002
# No N+1 query, but iterating through each payment in Ruby
# is grossly inefficient as well
amount - payments.map(:amount).inject(0, :+)
end
def balance_003
# Even with the "includes" in the controller, this is N+1
amount - (payments.sum(:amount) || 0)
end
end
Now my question is how to do this all the time with my Loan model. Normally ActiveRecord loads one or more models using the following query:
SELECT * FROM loans
--where clause optional
WHERE id IN (?)
Is there any way to override the Loan model so that it loads the following query:
SELECT
loans.*, loans.amount - SUM(payments.amount) as balance
FROM
loans
LEFT JOIN
payments ON payments.loan_id = loans.id
GROUP BY
loans.id
This way "balance" is a property of the model and only has to be declared in one place, but we also avoid the inefficiencies of N+1 queries.