2009-12-10 83 views
3

我發佈了一個similar question這個不久之前,關於格式化MySQL查詢使用塊,並得到了非常好的迴應,但他們是非常具體的問題。這一次,我正在處理在表格中獲取行的.sum()。這裏是我現在得到的:在紅寶石中添加塊

def balance 
    balance = 0 
    items.each do |item| 
    balance = balance + item.charges.sum(:revenue, :conditions => ['created_at >= ?', Time.now.beginning_of_month]) 
    end 
    balance 
end 

我的目標是獲取給定用戶的本月所有費用的總和。費用屬於屬於用戶的項目。我相信在Ruby/Rails中有更好的方法來做到這一點。

你會怎麼做?

+3

我不知道爲什麼在這裏的幾個人似乎在迭代過結果的方法,這是昂貴的,當你可以做到使用ActiveRecord提供了方法,SQL整個計算跳躍。這裏不需要.each,.map,.reduce或.inject。 – 2009-12-10 20:15:18

+0

請在我的回答後看到我的評論。我測試過它,它工作。這正是SQL連接的情況,沒有理由迭代你的結果,或者做多個查詢(Ian的第二個代碼就是這樣),這兩者都會給你的項目增加不必要的複雜性和處理。 – 2009-12-10 20:52:15

+0

@Jordan:我同意。我自己,我太瞭解OP的問題了(如何在塊中總結數字),而不是看實際的*問題*(從SQL表中求和)。 – 2009-12-10 21:20:02

回答

5

直轉換可以做到:

def balance 
    conds = ["created_at > ?", Time.now.beginning_of_month] 
    items.inject(0) do |total, item| 
    total + item.charges.sum(:revenue, :conditions => conds) 
    end 
end 

有可能是取決於你的關係是如何映射出更優化的方法。舉例來說,你可以做一些事情,如:

def balance 
    Charge.sum :revenue, 
    :conditions => ["charges.item_id IN (?) AND created_at > ?", 
     items.map { |item| item.id }, 
     Time.now.beginning_of_month] 
end 

對於這類情況,mapinjectselect,等等,是非常寶貴的工具。 Here's a lengthy discussion on inject,有關詳細信息,請參閱RDoc's for the Enumerable模塊。

+1

如果您將第一個示例中的變量「acc」重命名爲sum,則可能會更清楚。 – 2009-12-10 20:08:48

+1

此外,可以將'Time.now.beginning_of_month'存儲在'inject'塊之外的變量中,所以它不會在每次迭代中重新計算。 – 2009-12-10 20:09:20

+0

你的第二個解決方案奇妙地工作 – bloudermilk 2009-12-10 20:48:42

2

沒有理由不這樣做在SQL查詢本身,e.g:

Charges.sum :revenue, :conditions => [ "created_at >= ?, items.user_id = ?", 
             Time.now.beginning_of_month, some_user_id ], 
         :joins => :items 

編輯:這是從文檔sum是否會採取一個符號不清楚:加入像find一樣。如果沒有,你的:加入行應該是這樣,而不是:

:joins => "JOIN items ON charges.item_id = items.id" 
+0

Jordan,不幸的是,user_id沒有存儲在收費表中,只是項目表。 – bloudermilk 2009-12-10 20:28:18

+0

Bloudermilk:這就是JOIN的用途(我最初使它比需要的更復雜;我已經修復了它)。如果費用belongs_to項目和項目belongs_to用戶,您可以使用項目表加入以匹配user_id。這就是連接的關鍵,你可以在SQL中完成整個計算。 – 2009-12-10 20:34:13

1

只是一對夫婦的一般性評論:

  • 我建議把你的代碼模型,而不是一個控制器,查看或助手,遵循Skinny Controller, Fat Model的想法。
  • 而不是寫balance = balance + item.charges.sum(:revenue, :conditions => ['created_at >= ?', Time.now.beginning_of_month])你可以使用+=和做:balance += item.charges.sum(:revenue, :conditions => ['created_at >= ?', Time.now.beginning_of_month])
  • 這在技術上並不是一個問題,但它有一個與您的方法同名的變量(即「平衡」)似乎很糟糕。
  • 我會將Time.now.beginning_of_month存儲在循環外的變量中,因此每次都不會重新計算。
+0

謝謝莎拉,那種方法來自模型。我不知道Ruby支持+ =!關於在循環外存儲Time.now.beginning_of_month的好主意 – bloudermilk 2009-12-10 20:27:29

1

這是一個非常常見的遞歸模式。它被稱爲catamorphsim in category theory,a fold in mathematics and functional programming,它有時也被稱爲減少而在Smalltalk中它被稱爲inject:into:。在Ruby中,它被稱爲injectreduce(這兩個方法都是別名)。

這個想法是,你有一個值的集合,你想「減少」或「摺疊」多個值的集合成一個單一的值。 (Smalltalk名稱inject:into:來自這樣一個事實,即您將初始值插入到爲集合中的每個元素調用的塊中。)

def balance 
    this_month = Time.now.beginning_of_month 
    items.reduce(0) { |balance, item| 
    balance + item.charges.sum(:revenue, :conditions => ['created_at >= ?', this_month]) 
    } 
end 
+0

可能遵循Ruby慣例,在多行上使用'do'和'end'作爲塊。 – 2009-12-10 20:53:23

+1

這裏有兩個陣營:一個陣營使用'do' /'end'作爲多行,''''''''作爲單行。另一個使用'''''''end'作爲命令式,''''''''使用功能式樣式塊。我屬於後者陣營。 – 2009-12-10 21:09:52