Rails 2.3 - 使用mixin实现动态的named_scope

时间:2011-09-13 09:24:39

标签: ruby-on-rails activerecord refactoring named-scope mixins

我使用以下method_missing实现为特定模型提供适应性的named_scope过滤:

class Product < ActiveRecord::Base
  def self.method_missing(method_id, *args)

    # only respond to methods that begin with 'by_'
    if method_id.to_s =~ /^(by\_){1}\w*/i

      # extract column name from called method
      column = method_id.to_s.split('by_').last

      # if a valid column, create a dynamic named_scope
      # for it. So basically, I can now run
      # >>> Product.by_name('jellybeans')
      # >>> Product.by_vendor('Cyberdine')
      if self.respond_to?( column.to_sym )
        self.send(:named_scope, method_id, lambda {|val|
          if val.present?
            # (this is simplified, I know about ActiveRecord::Base#find_by_..)
            { :conditions => ["#{base.table_name}.#{column} = ?", val]}
          else
            {}
          end
        })
      else
        super(method_id, args)
      end
    end
  end
end

我知道ActiveRecord :: Base已经使用find_by_<X>提供了这个,但是我试图超出我给出的示例,并为我的应用程序提供了一些自定义过滤。我想将它提供给选定的模型,而不必将这个片段粘贴到每个模型类中。我想过使用一个模块,然后将它混合在所选择的模型中 - 我对语法有点模糊。

当错误开始堆积时,我已经达到了这一点(我这样做了吗?):

module GenericFilter
  def self.extended(base)

    base.send(:method_missing, method_id, *args, lambda { |method_id, args|
    # ?..
    })

  end
end

然后我希望能够像这样使用它:

def Product < ActiveRecord::Base
  include GenericFilter
end

def Vendor < ActiveRecord::Base
  include GenericFilter
end

# etc..

任何帮助都会很棒 - 谢谢。

2 个答案:

答案 0 :(得分:2)

实现此目的的两种方法

 module GenericModule
   def self.included(base)
     base.extend ClassMethods
   end

   module ClassMethods
     def methods_missing
       #....
     end
   end
 end

 class YourModel 
   include GenericModule
   .. 
 end

  module GenericModule
    def method_missing
      #...
    end
  end

  class MyModel
    extend GenericModule
  end

我建议使用第一个,它对我来说似乎更清洁。一般建议,我会避免重写method_missing:)。

希望这有帮助。

答案 1 :(得分:1)

您需要在包含mixin的类的上下文中定义范围。将您的范围包含在include_class.class_eval中,self将正确设置为includes_class。

module Mixin
  def self.included(klass)
    klass.class_eval do
      scope :scope_name, lambda {|*args| ... }
    end
  end
end

class MyModel
  include Mixin
end