我已经定义了一个扩展ActiveRecord的模块。
在我的情况下,我必须使用作为compound_datetime
类方法的参数给出的符号生成实例方法。在class_eval
块之外调用each
但不在其中时调用它;在后一种情况下,我得到一个未定义的方法错误。
有谁知道我做错了什么?
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
当我尝试访问该方法时:
Account.new.sales_at_to
我得到MethodError: undefined method sales_at_to for #<Account:0x007fd7910235a8>
。
答案 0 :(得分:3)
您正在define_compounds(DateTimeComposer::attrs)
模块定义的末尾调用InstanceMethods
。在代码中的那一点,attrs
仍然是一个空数组,而self
是InstanceMethods
模块。
这意味着不会定义任何方法,即使它们是,它们也会绑定到InstanceMethods
的元类,使它们成为该模块的类方法,而不是实例方法你的Account
课程。
这是因为InstanceMethods
模块定义中的方法调用是在ruby解释器看到的时候被评估的,当你调用include ActiveRecordExtensions::InstanceMethods
时不是。这意味着it is possible to run arbitrary code in the most unusual of places, such as within a class definition。
要解决此问题,您可以使用ruby提供的included
callback,只要模块包含在另一个模块中,就会调用它:
module InstanceMethods
# mod is the Class or Module that included this module.
def included(mod)
DateTimeComposer::attrs.each do |attr|
mod.instance_eval <<-METHODS
def #{attr.to_s}_to
puts 'tes'
end
METHODS
end
end
end
作为一个额外的建议,您应该能够通过在调用compound_datetime
时简单地定义方法来实现相同的结果,从而消除对attrs
全局类变量的依赖。
但是,如果您必须有权访问声明为复合日期时间的字段,则应使用类实例变量,这些变量对于每个类都是唯一的,而不是在层次结构上共享:
module ClassMethods
def compound_datetime(*attrs)
@datetime_compounds = attrs
attrs.each do |attr|
instance_eval <<-METHODS
def #{attr.to_s}_to
puts 'tes'
end
METHODS
end
end
def datetime_compounds; @datetime_compounds; end;
end
class Account < ActiveRecord::Base
compound_datetime :sales_at, :published_at
end
class AnotherModel < ActiveRecord::Base
compound_datetime :attr1, :attr2
end
Account.datetime_compounds
=> [ :sales_at, :published_at ]
AnotherModel.datetime_compounds
=> [ :attr1, :attr2 ]