包含InstanceMethods模块时覆盖attr_accessor的setter方法

时间:2011-11-07 21:42:29

标签: ruby-on-rails ruby ruby-on-rails-3 ruby-on-rails-3.1

我有一个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方法吗?

2 个答案:

答案 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