我有一个ActiveRecord扩展(缩写):
module HasPublishDates def self.included(base) base.send :extend,ClassMethods end module ClassMethods def has_publish_dates(*args) attr_accessor :never_expire include InstanceMethods end end module InstanceMethods def never_expire=(value) @never_expire = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value) end def another_instance_method 'something to return' end end end ActiveSupport.on_load(:active_record) do include HasPublishDates end
可以像这样调用:
class MyModel < ActiveRecord::Base has_publish_dates ... end
我们的想法是,never_expire =应该覆盖由attr_accessor:never_expire定义的setter.但是,它似乎没有工作:
m = MyModel.new m.never_expire #=> nil m.never_expire = '1' #=> '1' m.never_expire #=> '1' should be true if never_expire= has been overridden m.another_instance_method #=> 'something to return' works as expected
正如您所看到的,正在包含another_instance_method并且正在按预期工作但never_expire =并未像我预期的那样覆盖setter.
如果我将HasPublishDates更改为使用class_eval,那么它将按预期工作:
module HasPublishDates ... module ClassMethods def has_publish_dates(*args) ... class_eval do def never_expire=(value) @never_expire = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value) end def another_instance_method 'something to return' end end end end end ... m = MyModel.new m.never_expire #=> nil m.never_expire = '1' #=> true m.never_expire #=> true m.another_instance_method #=> 'something to return'
我想这是因为InstanceMethods是在attr_accessor之前定义的:never_expire是由has_publish_dates调用的.
虽然我认为class_eval是一种优雅的做事方式,但我也喜欢将我的实例方法暴露给文档的想法,所以当另一个开发人员试图使用我的代码时,没有“魔力”.
无论如何我可以在这种情况下使用include InstanceMethods方法吗?
解决方法
在继续使用包含的模块和超类方法的方法之前,Ruby中的调用顺序以普通实例方法开始.由attr_accessor创建的never_expire =方法最终成为一个实例方法,因此调用它而不是InstanceMethods模块的方法.如果你使用attr_reader,那么没有定义never_expire = instance方法,它将按你的意愿工作.
也就是说,使用那些额外的ClassMethods和InstanceMethods模块,你所做的事情比他们需要的更复杂.只需按照预期使用模块:
module HasPublishDates attr_reader :never_expire def never_expire=(value) @never_expire = ActiveRecord::ConnectionAdapters::Column.value_to_boolean(value) end end class MyModel < ActiveRecord::Base include HasPublishDates end