I have defined a module to extend ActiveRecord.
In my case I have to generate instance methods with the symbols given as arguments to the compound_datetime class method. It works when class_eval is called outside the each block but not inside it; in the latter case I get an undefined method error.
Does anyone know what I am doing wrong?
module DateTimeComposer
mattr_accessor :attrs
@@attrs = []
module ActiveRecordExtensions
module ClassMethods
def compound_datetime(*attrs)
DateTimeComposer::attrs = attrs
include ActiveRecordExtensions::InstanceMethods
end
end
module InstanceMethods
def datetime_compounds
DateTimeComposer::attrs
end
def self.define_compounds(attrs)
attrs.each do |attr|
class_eval <<-METHODS
def #{attr.to_s}_to()
puts 'tes'
end
METHODS
end
end
define_compounds(DateTimeComposer::attrs)
end
end
end
class Account < ActiveRecord::Base
compound_datetime :sales_at, :published_at
end
When I try to access the method:
Account.new.sales_at_to
I get a MethodError: undefined method sales_at_to for #<Account:0x007fd7910235a8>.
You are calling
define_compounds(DateTimeComposer::attrs)at the end of theInstanceMethodsmodule definition. At that point in the code,attrsis still an empty array, andselfis theInstanceMethodsmodule.This means no methods will be defined, and even if they were, they would be bound to
InstanceMethods‘s metaclass, making them class methods of that module, not instance methods of yourAccountclass.This happens because method calls inside the
InstanceMethodsmodule definition are evaluated as they are seen by the ruby interpreter, not when you callinclude ActiveRecordExtensions::InstanceMethods. An implication of this is that it is possible to run arbitrary code in the most unusual of places, such as within a class definition.To solve the problem, you could use the
includedcallback provided by ruby, which is called whenever a module is included in another:As an additional suggestion, you should be able to achieve the same result by simply defining the methods when
compound_datetimeis called, thus eliminating the dependence on theattrsglobal class variable.However, if you must have access to the fields which were declared as compound datetime, you should use class instance variables, which are unique to each class and not shared on the hierarchy: