Rails 2.3 — реализовать динамическую named_scope с помощью миксина

Я использую следующую реализацию 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..

Любая помощь будет отличной - спасибо.


person sa125    schedule 13.09.2011    source источник


Ответы (2)


Два способа достижения этого

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

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

 class YourModel 
   include GenericModule
   .. 
 end

or

  module GenericModule
    def method_missing
      #...
    end
  end

  class MyModel
    extend GenericModule
  end

Я бы предложил использовать первый, он кажется мне чище. И, как правило, я бы не стал переопределять method_missing :).

Надеюсь это поможет.

person Rishav Rastogi    schedule 13.09.2011

Вам нужно определить область действия в контексте класса, в который входит ваш миксин. Оберните свои области видимости в include_class.class_eval, и self будет правильно установлен в include_class.

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

class MyModel
  include Mixin
end
person Jeremy    schedule 14.02.2012