2011-05-10 72 views
1

我發現自己在兩個地方編寫了非常類似的代碼,一次在模型上定義(虛擬)布爾屬性,一次定義範圍以查找匹配該條件的記錄。實質上,Rails 3:如何編寫DRYer範圍

scope :something, where(some_complex_conditions) 

def something? 
    some_complex_conditions 
end 

一個簡單的例子:我正在建模俱樂部會員;一個Member支付Fee,僅在某個year有效。

class Member < ActiveRecord::Base 
    has_many :payments 
    has_many :fees, :through => :payments 

    scope :current, joins(:fees).merge(Fee.current) 

    def current? 
    fees.current.exists? 
    end 
end 

class Fee < ActiveRecord::Base 
    has_many :payments 
    has_many :members, :through => :payments 

    scope :current, where(:year => Time.now.year) 

    def current? 
    year == Time.now.year 
    end 
end 

是否有一個乾燥器的方式來寫一個範圍,使利用虛擬屬性(或,可替代地,爲了確定模型是否由範圍的條件相匹配)?

我對Rails很新,所以請指出我是否在做一些愚蠢的事情!

回答

0

不,沒有更好的方式去做你正在做的事情(除了注意到Geraud的評論)。在你的作用域中,你定義了一個類級別的過濾器,它將生成SQL來限制你的finder返回的結果,在你定義的實例級測試的屬性中,該類將在該類的特定實例上運行。

是的,代碼是相似的,但它在不同的上下文中執行不同的功能。

0

是的,你可以在你的作用域中使用一個或多個帶lambda的參數。假設你有一組項目,你想回到那些是「引導」或「頭盔」:

scope :item_type, lambda { |item_type| 
    where("game_items.item_type = ?", item_type) 
    } 

現在可以做game_item.item_type(「引導」),僅保留靴子或game_item.item_type('頭盔')只獲得頭盔。你的情況同樣適用。您可以在您的範圍中只包含一個參數,以DRYer方式檢查同一範圍內的一個或多個條件。

+0

謝謝,不過,雖然這會讓我以DRY的方式編寫多個相關的作用域,但我不確定這樣可以幫助我編寫一個類似'boot?'的方法,而不必重複'item_type =='Boot'邏輯。 – 2011-05-10 21:41:00

+0

你也可以做到這一點。只需創建一個像is(type)那樣的方法或範圍並像使用它('Boot')那樣使用它。然後,您的finder根據您直接提供的字符串選擇項目,這樣您就不必每次都檢查item_type =='Boot'。 – Spyros 2011-05-10 22:00:52

+0

def boot?; GameItem.item_type('Boot')。where(:id => self.id).exists?;結束 – 2011-08-10 18:54:41

1

這不是對問題的回答,而是您的代碼有一個錯誤(如果您在生產中使用類似的東西):Time.now.year將返回服務器啓動的一年。你想在一個lambda中運行這個範圍,以使其行爲像預期的那樣。

scope :current, lambda { where(:year => Time.now.year) } 
+0

在OP的例子中,當代碼第一次執行而不是在服務器啓動時計算時間?或者與服務器啓動同義。 – danneu 2011-05-10 21:48:06

+0

謝謝,很好。 @Dobry:我認爲你是對的,Rails會根據需要加載模型,所以它不會在服務器啓動時完成......但無論如何這不是預期的效果! – 2011-05-10 22:00:26

+0

@dobry:你是完全正確的。這就是我的意思,我應該更準確。 – Geraud 2011-05-11 00:25:04