2011-11-07 90 views
7

當我有一個ActiveRecord擴展名(略):重寫setter方法包括InstanceMethods模塊

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=應重寫setter由attr_accessor :never_expire定義。然而,這似乎並不奏效:

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' 

我想,這是因爲之前attr_accessor :never_expire被稱爲has_publish_datesInstanceMethods定義。

儘管我認爲class_eval是做的事情我也喜歡有暴露我的實例方法的文檔,所以沒有「神奇」時,另一位開發人員正試圖用我的代碼的想法的一種優雅的方式。

反正我有可以使用include InstanceMethods辦法在這種情況下?

回答

10

電話以便與正常情況下的方法開始着手包含的模塊和超類方法的方法之前, 。通過attr_accessor創建的never_expire=方法捲起成爲一個實例方法,所以它被稱爲而非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

嗯,你可能只是不attr_accessor中費心......畢竟,你只需要添加:

def never_expire 
    @never_expire 
end 

,它會工作得很好沒有這種。

如果它是在DB的實​​際AR列,不過,我推薦使用

set_attribute(:never_expire, ActiveRecord::ConnectionAdapters::Column.... 

而非@never_expire變量。在這種情況下,你也不需要attr_accessor。

A中的最後一個選項,你可以在包含語句如使用類-EVAL 只是:在Ruby中

module ClassMethods 
    def has_publish_dates(*args) 
    attr_accessor :never_expire 

    class_eval do 
     include InstanceMethods 
    end 
    end 
end