2013-10-11 47 views
2

在我的Rails應用程序中,我有users誰可以有很多payments如何在Rails模型中更新實例變量?

class User < ActiveRecord::Base 

    has_many :invoices 
    has_many :payments 

    def year_ranges 
    ... 
    end 

    def quarter_ranges 
    ... 
    end 

    def month_ranges 
    ... 
    end 

    def revenue_between(range, kind) 
    payments.sum_within_range(range, kind) 
    end 

end 

class Invoice < ActiveRecord::Base 

    belongs_to :user 
    has_many :items 
    has_many :payments 

    ... 

end 

class Payment < ActiveRecord::Base 

    belongs_to :user 
    belongs_to :invoice 

    def net_amount 
    invoice.subtotal * percent_of_invoice_total/100 
    end 

    def taxable_amount 
    invoice.total_tax * percent_of_invoice_total/100 
    end 

    def gross_amount 
    invoice.total * percent_of_invoice_total/100 
    end 

    def self.chart_data(ranges, unit) 
    ranges.map do |r| { 
     :range   => range_label(r, unit), 
     :gross_revenue => sum_within_range(r, :gross), 
     :taxable_revenue => sum_within_range(r, :taxable), 
     :net_revenue  => sum_within_range(r, :net) } 
    end 
    end 

    def self.sum_within_range(range, kind) 
    @sum ||= includes(:invoice => :items) 
    @sum.select { |x| range.cover? x.date }.sum(&:"#{kind}_amount") 
    end 

end 

在我dashboard鑑於我取決於用戶選擇了GET參數列出了ranges總付款。用戶可以選擇years,quartersmonths

class DashboardController < ApplicationController 

    def show 
    if %w[year quarter month].include?(params[:by]) 
     @unit = params[:by] 
    else 
     @unit = 'year' 
    end 
    @ranges = @user.send("#{@unit}_ranges") 
    @paginated_ranges = @ranges.paginate(:page => params[:page], :per_page => 10) 
    @title = "All your payments" 
    end 

end 

的使用實例變量(@sum)大大降低SQL的數量在這裏查詢,因爲該數據庫將不會越過命中爲相同的查詢一遍又一遍。

但問題是,當用戶創建,刪除或更改他的某個payments時,這不會反映在@sum實例變量中。那麼我該如何重置它?還是有更好的解決方案呢?

感謝您的任何幫助。

回答

0

也許你可以觀察員做到這一點:

# payment.rb 

def self.cached_sum(force=false) 
    if @sum.blank? || force 
    @sum = includes(:invoice => :items) 
    end 
    @sum 
end 

def self.sum_within_range(range) 
    @sum = cached_sum 
    @sum.select { |x| range.cover? x.date }.sum(&total) 
end 

#payment_observer.rb 

class PaymentObserver < ActiveRecord::Observer 
    # force @sum updating 

    def after_save(comment) 
    Payment.cached_sum(true) 
    end 

    def after_destroy(comment) 
    Payment.cached_sum(true) 
    end 

end 

你可以找到更多關於觀察員http://apidock.com/rails/v3.2.13/ActiveRecord/Observer

0

嗯,你@sum基本上是你所需要的值的緩存。像任何緩存一樣,如果所涉及的值發生變化,您需要使其無效。

您可以使用after_saveafter_create過濾器調用設置爲@sum = nil的函數。還可以保存緩存覆蓋的範圍,並根據新的或更改的付款日期確定失效情況。

class Payment < ActiveRecord::Base 

    belongs_to :user 

    after_save :invalidate_cache 

    def self.sum_within_range(range) 
    @cached_range = range 
    @sum ||= includes(:invoice => :items) 
    @sum.select { |x| range.cover? x.date }.sum(&total) 
    end 

    def self.invalidate_cache 
    @sum = nil if @cached_range.includes?(payment_date) 
end 
3

而不是存儲的關聯作爲類Payment的一個實例變量,將其存儲爲一個user的實例變量的(我知道這聽起來混亂,我試圖下面解釋)

class User < ActiveRecord::Base 

    has_many :payments 

    def revenue_between(range) 
    @payments_with_invoices ||= payments.includes(:invoice => :items).all 
    # @payments_with_invoices is an array now so cannot use Payment's class method on it 
    @payments_with_invoices.select { |x| range.cover? x.date }.sum(&:total) 
    end 

end 

當您在類方法中定義@sum(類方法由self.表示)時,它成爲類Payment的實例變量。這意味着您可以以Payment.sum的身份訪問它。所以這與特定用戶和他/她的付款無關。 @sum現在是類Payment的一個屬性,Rails將以與緩存類的方法定義相同的方式對其進行緩存。

一旦@sum被初始化,它將保持不變,就像您注意到的那樣,即使在用戶創建新付款後或者其他用戶登錄該事件!它會在應用程序重新啓動時更改。

但是,如果您像上面顯示的那樣定義@payments_with_invoices,它將成爲User的特定實例的屬性,換句話說就是實例級別的實例變量。這意味着您可以將其作爲some_user.payments_with_invoices進行訪問。由於一個應用程序可以有很多用戶,所以這些用戶不會跨越請求持久存儲在Rails內存中所以無論何時用戶實例更改其屬性再次加載。

所以如果用戶創建更多付款,@payments_with_invoices變量將被刷新,因爲用戶實例被重新初始化。

+0

+1,很好的解決方法。雖然我會讓'sum_within_range'做所有使用SQL的計算(請參閱我的回答) –

+0

@m_x ya通常情況會更好,但由於prev問題,我有一些bkgrnd。這個函數在相同的重疊範圍請求中被調用了很多次,所以我認爲最好在Rails中進行過濾。但如果不是這樣的話,你的解決方案更有意義:) – tihom

+0

謝謝。當我複製你的確切代碼時,我仍然可以獲得大約80個SQL查詢。所以我將'includes(:invoice =>:items)'移動到'Payment'類的select方法的開頭,現在每頁只有大約10個SQL查詢(與之前相同,因此也很好),但是總渲染時間已經增加了很多,從大約300毫秒到大約3000毫秒。這絕對是太多了,我想知道它爲什麼。 – Tintin81

3

這是附帶的問題,但不要使用#select和塊。

你在做什麼是選擇所有付款,然後過濾關係作爲一個數組。使用Arel來解決這個問題:

scope :within_range, ->(range){ where date: range } 

這將構建一個SQL BETWEEN語句。在生成的關係上使用#sum將構建一條SQL SUM()語句,該語句可能比加載所有記錄更有效。

+0

'select'是一個ActiveRecord查詢方法,而不是數組。這只是範圍上的另一個鏈條。 –

+3

不適用,不適用於塊。嘗試一下:它會加載所有記錄。 –

+0

證明:http://apidock.com/rails/ActiveRecord/QueryMethods/select。 「與塊,工作就像陣列#選擇」看到源... –