2010-03-04 34 views
4

我有一堆命名範圍,並且在其中一個範圍內有一個我希望在其他命名範圍之間共享的方法。我通過使用define_method和lambda完成了這項工作。但是,仍然有一些重複的代碼,我想知道是否有更好的方法?在指定範圍之間共享方法

下面是我得到的一個簡單的例子。假設我有一個項目表,每個項目都有很多用戶。

內的用戶模型我有...

filter_by_name = lambda { |name| detect {|user| user.name == name} } 

named_scope :active, :conditions => {:active => true} do 
    define_method :filter_by_name, filter_by_name 
end 

named_scope :inactive, :conditions => {:active => false} do 
    define_method :filter_by_name, filter_by_name 
end 

named_scope :have_logged_in, :conditions => {:logged_in => true} do 
    define_method :filter_by_name, filter_by_name 
end 

然後我會使用它像...

active_users = Project.find(1).users.active 

some_users = active_users.filter_by_name ["Pete", "Alan"] 
other_users = active_users.filter_by_name "Rob" 

logged_in_users = Project.find(1).users.logged_in 

more_users = logged_in_users.filter_by_name "John" 

回答

2

這是一個完全不同的解決方案,可能更加精確地解決問題的要求。

named_scope需要一個塊,它可以是任何Proc。因此,如果您創建一個定義了filter_by_name方法的lambda/Proc,則可以將其作爲named_scope的最後一個參數傳遞。

filter_by_name = lambda { |name| detect {|user| user.name == name} } 

add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name } 

named_scope(:active, :conditions => {:active => true}, &add_filter_by_name) 

named_scope(:inactive, :conditions => {:active => false}, &add_filter_by_name) 

named_scope(:have_logged_in, :conditions => {:logged_in => true}, &add_filter_by_name) 

這會做你想要的。如果您仍然認爲它太重複了,您可以將它與mrjake2解決方案中的技術結合使用,以便一次定義多個命名範圍。類似這樣的:

method_params = { 
    :active => { :active => true }, 
    :inactive => { :active => false }, 
    :have_logged_in => { :logged_in => true } 
} 

filter_by_name = lambda { |name| detect {|user| user.name == name} } 

add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name } 

method_params.keys.each do |method_name| 
    send(:named_scope method_name, :conditions => method_params[method_name], 
    &add_filter_by_name) 
end 
+0

我真的很喜歡你的方法。我忘記了你可以使用&方法傳遞一個塊。它完全按照我的需要工作,謝謝! –

1

我可能會使用一些元編程:

method_params = { 
    :active => { :active => true }, 
    :inactive => { :active => false }, 
    :have_logged_in => { :logged_in => true } 
} 

method_params.keys.each do |method_name| 
    send :named_scope method_name, :conditions => method_params[method_name] do 
    define_method :filter_by_name, filter_by_name 
    end 
end 

這種方式如果你想在未來添加更多的發現者,你可以添加方法名稱和條件的methods_param哈哈SH。

+0

感謝您的意見。我會測試一下,看看會發生什麼。我需要使用'send:named_scope'嗎?爲什麼不直接調用named_scope? –

+0

這是因爲你在每個循環中 - 因爲你在一個代碼塊內,所以你不再處於這個級別。 – mrjake2

2

命名示波器可以被鏈接,所以你使自己的努力比你需要的更難。

的用戶模型中定義會得到你想要的東西時,以下幾點:

class User < ActiveRecord::Base 
    ... 
    named_scope :filter_by_name, lambda { |name| 
    {:conditions => { :name => name} } 
    } 

    named_scope :active, :conditions => {:active => true} 

    named_scope :inactive, :conditions => {:active => false} 

    named_scope :have_logged_in, :conditions => {:logged_in => true} 
end 

然後下面的代碼段將工作:

active_users = Project.find(1).users.active 

some_users = active_users.filter_by_name(["Pete", "Alan"] 
other_users = active_users.filter_by_name "Rob" 

logged_in_users = Project.find(1).users.have_logged_in 

more_users = logged_in_users.filter_by_name "John" 

我看到你正在使用detect可能,以避免數據庫過量命中。但是你的例子沒有正確使用它。 Detect僅返回該塊返回true的列表中的第一個元素。在上面的示例中,some_users只會是單個記錄,即第一個名爲「Pete」或「Alan」的用戶。如果您希望獲得名爲「Pete」或「Alan」的所有用戶,那麼您需要select。如果你想select那麼你最好使用命名的範圍。

命名作用域在評估時返回一個特殊對象,該對象包含構建SQL語句以生成結果所需的組件,鏈接其他命名作用域仍然不執行該語句。直到您嘗試訪問結果集上的方法,例如調用每個或地圖。

+0

我忘了命名範圍可以鏈接 - 絕對像您的解決方案更好。 – mrjake2

+0

謝謝你的建議。我意識到這是可能的,但我試圖避免多餘的分貝查詢。在我的測試中,我發現將'active_users'上的其他命名作用域鏈接在一起會再次構建整個查詢並執行它,而不僅僅是從內存中過濾掉對象。我的例子非常精簡,但實際的代碼有幾個連接的模型和條件,所以這種優化似乎很重要。 –

0

您也可以使用第二個命名範圍做到這一點。

named_scope :active, :conditions => {:active => true} 
named_scope :inactive, :conditions => {:active => false} 
named_scope :have_logged_in, :conditions => {:logged_in => true} 
named_scope :filter_by_name, lambda {|name| :conditions => ["first_name = ? OR last_name = ?", name, name]} 

然後你可以做@project.users.active.filter_by_name('Francis')

如果你真的需要可枚舉#做這個檢測,我會定義一個模塊中的filter_by_name方法,然後可以擴展命名範圍:

with_options(:extend => FilterUsersByName) do |fubn| 
    fubn.named_scope :active, :conditions => {:active => true} 
    fubn.named_scope :inactive, :conditions => {:active => false} 
    fubn.named_scope :have_logged_in, :conditions => {:logged_in => true} 
end 

module FilterUsersByName 
    def filter_by_name(name) 
    detect {|user| user.name == name} 
    end 
end 

這增加了filter_by_name方法返回的類所有三個有名望的範圍。