2015-11-21 34 views
1

我有一個「貸款」的模式在Rails中,我試圖建立。有一個相應的「付款」模式。貸款餘額是貸款的原始金額減去所有付款的總和。計算餘額很容易,但我試圖計算大量貸款的餘額,同時避免N + 1查詢,同時使「餘額」成爲「貸款」模型的一個屬性。Rails中,使用自定義的SQL查詢來填充ActiveRecord的模式

當我打電話貸款控制器的索引方法,我可以運行一個自定義選擇查詢,這讓我通過直接的SQL查詢返回一個「平衡」的屬性。

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 

現在我的問題是如何隨時使用我的貸款模式做到這一點。通常情況下的ActiveRecord加載一個或使用以下查詢更多型號:

SELECT * FROM loans 
--where clause optional 
WHERE id IN (?) 

有沒有什麼辦法,以便它加載下面的查詢重寫貸款模式:

SELECT 
    loans.*, loans.amount - SUM(payments.amount) as balance 
FROM 
    loans 
LEFT JOIN 
    payments ON payments.loan_id = loans.id 
GROUP BY 
    loans.id 

這樣的「平衡」是一個模型的屬性,只需要在一個地方聲明,但我們也避免了N + 1查詢的低效率。

回答

1

我喜歡使用數據庫視圖此,使鋼軌認爲它跟一個常規數據庫表(保證之類的預先加載正常工作)時,其實有聚合或複雜的連接回事。在你的情況,我可以定義一個第二loan_balances觀點:

create view loan_balances as (
    select loans.id as loan_id, loans.amount - sum(payments.amount) as balance 
    from loans 
    left outer join payments on payments.loan_id = loans.id 
    group by 1 
) 

然後,只需做定期的軌道關聯的東西:

class LoanBalance < ActiveRecord::Base 
    belongs_to :loan, inverse_of: :loan_balance 
end 

class Loan < ActiveRecord::Base 
    has_one :loan_balance, inverse_of: :loan 
    delegate :balance, to: :loan_balance, prefix: false 
end 

這樣在你想要的平衡與includes(:loan_balance)你可以貪婪加載它的行動,但你不會因違反貸款本身的所有標準CRUD內容而違反Rails慣例而陷入棘手的問題。

+0

這種方法的問題是大多數數據庫視圖不能被索引。這意味着要查找記錄,您必須對視圖執行線性掃描。我相信Microsoft SQL Server允許索引視圖。據我所知,Postgres沒有。 –

+1

@SteveZelaznik,這不完全正確。除非物化,否則不能在視圖上創建索引,但是,postgres應該在普通視圖中使用底層表上的索引。 – spike

0

它看起來像我終於回答了我的問題。這裏是。我超過了默認範圍。

class Loan < ActiveRecord::Base 
    validates :funded_amount, presence: true, numericality: {greater_than: 0} 
    has_many :payments, dependent: :destroy, inverse_of: :loan 
    default_scope { 
    joins("LEFT JOIN payments as p ON p.loan_id = loans.id") 
    .group("loans.id").select("loans.*, sum(p.amount) as paid") 
    } 
    def balance 
    funded_amount - (paid || 0) 
    end 
end