2011-11-07 9 views
7

Ho un ActiveRecord estensione (abbreviato):overriding metodo setter per attr_accessor quando comprese InstanceMethods modulo

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 

che può essere definito così:

class MyModel < ActiveRecord::Base 
    has_publish_dates 
    ... 
end 

L'idea è che never_expire= dovrebbe ignorare la setter definito da attr_accessor :never_expire. Tuttavia, non sembra funzionare:

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 

Come si può vedere, another_instance_method è stata inclusa e funziona come previsto, ma never_expire= non è prevalente il setter come mi aspettavo.

Se cambio HasPublishDates usare class_eval allora funziona come previsto:

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' 

immagino che questo è perché InstanceMethods è definito prima attr_accessor :never_expire è chiamato da has_publish_dates.

Anche se penso che class_eval sia un modo elegante di fare le cose mi piace anche l'idea di avere i miei metodi di istanza esposti per la documentazione, quindi non c'è "magia" quando un altro sviluppatore sta cercando di usare il mio codice.

È possibile utilizzare l'approccio include InstanceMethods in questo scenario?

risposta

10

ordine di chiamata in Ruby inizia con normali metodi di istanza prima di procedere ai metodi di moduli inclusi e metodi della superclasse . Il metodo never_expire= creato da attr_accessor è un metodo di istanza, quindi viene chiamato piuttosto che il metodo del modulo InstanceMethods. Se invece si utilizza attr_reader, in modo che non venga definito alcun metodo di istanza never_expire=, funzionerà come desiderato.

Detto questo, stai rendendo le cose più complicate di quelle che devono essere con quei moduli aggiuntivi ClassMethods e InstanceMethods. Basta usare i moduli come erano destinati:

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

Beh, si potrebbe semplicemente non perdete tempo con l'attr_accessor ... dopo tutto, si sarebbe solo bisogno di aggiungere:

def never_expire 
    @never_expire 
end 

e ci si lavora bene anche senza questo.

Se si tratta di una colonna AR reale sul db, però, mi consiglia di utilizzare

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

piuttosto che la variabile @never_expire. Non avresti nemmeno bisogno del attr_accessor in quel caso.

A una ultima opzione, è possibile utilizzare la classe-eval appena sulla esempio istruzione include:

module ClassMethods 
    def has_publish_dates(*args) 
    attr_accessor :never_expire 

    class_eval do 
     include InstanceMethods 
    end 
    end 
end 
Problemi correlati