ActiveRecord :: Base Extension

I am trying to add some special methods to ActiveRecord. I want to add *_after and *_before for each date field of the model so that I can do something like this:

 User.created_at_after(DateTime.now - 3.days).created_at_before(DateTime.now) 

I executed the Rails solution described here extending ActiveRecord :: Base , but when I start the rails console and try to call methods, I get an undefined method error.

Here is my code:

 # config/initializers/active_record_date_extension.rb require "active_record_date_extension" # lib/active_record_date_extension.rb module ActiveRecordDateExtension extend ActiveSupport::Concern included do |base| base.columns_hash.each do |column_name,column| if ["datetime","date"].include? column.type base.define_method("#{column_name}_after") do |date| where("#{column_name} > ?", date) end base.define_method("#{column_name}_before") do |date| where("#{column_name} < ?", date) end end end end end Rails.application.eager_load! ActiveRecord::Base.descendants.each do |model| model.send(:include, ActiveRecordDateExtension) end 

What am I doing wrong?

+8
ruby ruby-on-rails activerecord ruby-on-rails-4 metaprogramming
source share
2 answers

Thanks to the previous answer, I realized some of the problems. Here are all the problems and solutions I came to after some research:

  • column.type is a character and I compared it to a string.
  • base.define_method is a private method.
  • I needed to define methods in singleton_class , and not in the base class or class .
  • Rails.application.eager_load! will lead to high load, even if it is not required. This did not affect the functionality, but in the first place an intolerable load should not be responsible for this "extension", and secondly, it depends on Rails, which makes only the "extension" Rails compatible.

Given these problems, I decided to implement it using the method_missing ruby functionality, and I wrote this stone ( https://github.com/simon0191/date_supercharger ). Here is the relevant part for this question:

 module DateSupercharger extend ActiveSupport::Concern included do def self.method_missing(method_sym, *arguments, &block) return super unless descends_from_active_record? matcher = Matcher.new(self,method_sym) # Inside matcher # method_sym.to_s =~ /^(.+)_(before|after)$/ if matcher.match? method_definer = MethodDefiner.new(self) # self will be klass inside Matcher method_definer.define(attribute: matcher.attribute, suffix: matcher.suffix) # Inside MethodDefiner # new_method = "#{attribute}_#{suffix}" # operators = { after: ">", before: "<" } # klass.singleton_class.class_eval do # define_method(new_method) do |date| # where("#{attribute} #{operators[suffix]} ?", date) # end # end send(method_sym, *arguments) else super end end def self.respond_to?(method_sym, include_private = false) return super unless descends_from_active_record? if Matcher.new(self,method_sym).match? true else super end end end end ActiveRecord::Base.send :include, DateSupercharger 
+1
source share

Using Rails 4.1.9 and Ruby 2.2.1, I noticed several problems with the code above.

  • You compare column.type with rows, and Rails returns the characters for this attribute.
  • base.define_method trying to call a private method, you can get around this with send

This is a modified code.

 module ActiveRecordDateExtension extend ActiveSupport::Concern included do |base| base.columns_hash.each do |column_name,column| if [:datetime, :date].include? column.type base.class.send(:define_method, "#{column_name}_after") do |date| where("#{column_name} > ?", date) end base.class.send(:define_method, "#{column_name}_before") do |date| where("#{column_name} < ?", date) end end end end end 
+3
source share

All Articles