我有一堆命名范围,并且在其中一个范围内有一个方法,我希望在其他命名范围之间共享。我通过使用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"
答案 0 :(得分: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语句以生成结果所必需的组件,链接其他命名范围仍然不执行该语句。直到您尝试访问结果集上的方法,例如调用每个或映射。
答案 1 :(得分: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
答案 2 :(得分: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哈希。
答案 3 :(得分: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')
。
如果您确实需要使用Enumerable#detect来执行此操作,我将在模块中定义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方法添加到由所有三个命名范围返回的类中。