Ruby on rails 在命名作用域之间共享方法

Ruby on rails 在命名作用域之间共享方法,ruby-on-rails,activerecord,Ruby On Rails,Activerecord,我有一堆命名作用域,其中一个作用域中有一个方法,我想在其他命名作用域之间共享。我通过使用define_方法和lambda实现了这一点。然而,仍然有一些重复的代码,我想知道有没有更好的方法 这是我得到的一个简化示例。假设我有一个项目表,每个项目都有许多用户 在用户模型中,我有 filter_by_name = lambda { |name| detect {|user| user.name == name} } named_scope :active, :conditions => {:a

我有一堆命名作用域,其中一个作用域中有一个方法,我想在其他命名作用域之间共享。我通过使用define_方法和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"

我可能会使用一些元编程:

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

这样,如果您想在将来添加更多的查找程序,您只需将方法名称和条件添加到方法参数散列中。

命名范围可以链接,因此您自己将比需要更加困难

当在用户模型中定义时,以下内容将满足您的需求:

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
我看到您正在使用,可能是为了避免对DB的过多点击。但是你的例子没有正确地使用它。Detect仅返回块为其返回true的列表中的第一个元素。在上面的示例中,一些_用户将只是一条记录,第一个名为Pete或Alan的用户。如果您希望获得所有名为Pete或Alan的用户,那么您需要。如果要选择,最好使用命名范围

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]}

计算命名作用域时返回一个特殊对象,该对象包含生成SQL语句以生成结果所需的组件,链接其他命名作用域仍然不会执行该语句。除非您尝试访问结果集中的方法,例如调用each或map。

这里有一个完全不同的解决方案,可能更符合问题的精神

命名的_范围接受一个块,它可以是任何进程。因此,如果创建一个lambda/Proc来定义filter\u by\u name方法,则可以将其作为最后一个参数传递给命名的\u范围

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

您还可以使用第二个命名范围来执行此操作

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\u名称“Francis”

如果确实需要使用Enumerabledetect执行此操作,我将在一个模块中定义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\u by\u name方法添加到所有三个命名作用域返回的类中。

谢谢您的输入。我会测试一下,看看会发生什么。我是否需要使用send:named\u范围?为什么不直接调用命名的作用域呢?这是因为你在每个循环中-因为你在一个代码块中,所以你不再处于类级别。我忘了命名的作用域可以链接-肯定更喜欢你的解决方案。谢谢你的建议。我知道这是可能的,但我试图避免冗余的数据库查询。在我的测试中,我发现在活动用户上链接其他命名作用域基本上会再次构建整个查询并执行它,而不仅仅是从内存中过滤出对象。我的例子非常精简,但是实际的代码有一些连接的模型和条件,所以这种优化看起来很重要。我非常喜欢你的方法。我忘了你可以用&method通过一个街区。它完全符合我的需要,谢谢!