How to dynamically add class methods to Rails Models using module

6k views Asked by At

I have this code on model class to add search methods to be used by meta_search gem:

class Model

  self.def created_at_lteq_at_end_of_day(date)
     where("created_at <= ?", DateTime.strptime(date, '%d/%m/%Y').end_of_day)
  end

  search_methods :created_at_lteq_at_end_of_day
end

This code add search methods to date field. Now, is needed to add this search method to other classes and with others fields. To achieve this, this module was created:

lib/meta_search/extended_search_methods.rb

module MetaSearch
  module ExtendedSearchMethods
    def self.included(base)
      base.extend ClassMethods
    end


    module ClassMethods
      def add_search_by_end_of_day(field, format)

        method_name = "#{field}_lteq_end_of_day"

        define_method method_name do |date|
          where("#{field} <= ?", DateTime.strptime(date, format).end_of_day) 
        end

        search_methods method_name
      end
    end
  end
end


class Model
   include MetaSearch::ExtendedSearchMethods
   add_search_by_end_of_day :created_at, '%d/%m/%Y'
end

After add the module, this errors starts to rise:

undefined method `created_at_lteq_end_of_day' for #<ActiveRecord::Relation:0x007fcd3cdb0e28>

Other solution:

change define_method to define_singleton_method

2

There are 2 answers

1
Jef On BEST ANSWER

ActiveSupport provides a pretty idiomatic and cool way of doing that, ActiveSupport::Concern :

module Whatever
    extend ActiveSupport::Concern

    module ClassMethods
        def say_hello_to(to)
            puts "Hello #{to}"
        end
    end
end

class YourModel
    include Whatever

    say_hello_to "someone"
end

See the API doc. Although it is not directly related to your question, the included method is incredibly useful for models or controllers (scopes, helper methods, filters, etc), plus ActiveSupport::Concern handles dependencies between modules for free (both as in freedom and as in beer).

1
Dan Grahn On

You need to add a class << self so you are working in a singleton class.

module Library
  module Methods
    def self.included(base)
      base.extend ClassMethods
    end

    module ClassMethods
      def add_foo
        class << self
          define_method "foo" do
            puts "Foo!"
          end #define_method
        end #class << self
      end #add_foo
    end #ClassMethods
  end #Methods
end #Library

class Test
  include Library::Methods

  add_foo
end

puts Test.foo