我有一个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=
并没有像我预期的那样覆盖设置器。
如果我将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
方法吗?
答案 0 :(得分:11)
在继续使用包含模块和超类方法的方法之前,Ruby中的调用顺序以普通实例方法开始。由never_expire=
创建的attr_accessor
方法最终成为实例方法,因此调用它而不是InstanceMethods
模块的方法。如果您改为使用attr_reader
,那么没有定义never_expire=
实例方法,它将按您的意图运行。
也就是说,你使用那些额外的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
答案 1 :(得分:1)
好吧,你可以不打扰attr_accessor ......毕竟,你只需要添加:
def never_expire
@never_expire
end
如果没有它,它会正常工作。
如果它是db上的实际AR列,我建议使用
set_attribute(:never_expire, ActiveRecord::ConnectionAdapters::Column....
而不是@never_expire变量。在这种情况下你也不需要attr_accessor。
最后一个选项,您可以在include语句中使用class-eval ,例如:
module ClassMethods
def has_publish_dates(*args)
attr_accessor :never_expire
class_eval do
include InstanceMethods
end
end
end