Question

I use the following method_missing implementation to give a certain model an adaptable named_scope filtering:

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

I know this is already provided by ActiveRecord::Base using find_by_<X>, but I'm trying to go a little bit beyond the example I've given and provide some custom filtering taylored to my application. I'd like to make it available to selected models w/o having to paste this snippet in every model class. I thought of using a module and then mixing it in the models of choice - I'm just a bit vague on the syntax.

I've gotten as far as this when the errors started piling up (am I doing this right?):

module GenericFilter
  def self.extended(base)

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

  end
end

Then I hope to be able to use it like so:

def Product < ActiveRecord::Base
  include GenericFilter
end

def Vendor < ActiveRecord::Base
  include GenericFilter
end

# etc..

Any help will be great - thanks.

Was it helpful?

Solution

Two ways of achieving this

 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

I would suggest using the first one, its seems cleaner to me. And as general advise, I'd avoid overriding method_missing :).

Hope this helps.

OTHER TIPS

You need to define the scope within the context of the class that is including your mixin. Wrap your scopes in including_class.class_eval and self will be correctly set to the including_class.

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

class MyModel
  include Mixin
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top