在命名范围之间共享方法

时间:2010-03-04 16:19:59

标签: ruby-on-rails activerecord

我有一堆命名范围,并且在其中一个范围内有一个方法,我希望在其他命名范围之间共享。我通过使用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"

4 个答案:

答案 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方法添加到由所有三个命名范围返回的类中。