2009-10-03 10 views
17

Conozco ActiveRecord :: Dirty y los métodos relacionados, pero no veo un medio por el cual pueda suscribirme a un evento de cambio de atributo. Algo como:¿Devolución de llamada para los atributos modificados de ActiveRecord?

class Person < ActiveRecord::Base 
    def attribute_changed(attribute_name, old_value, new_value) 
    end 

    #or 

    attribute_changed do |attribute_name, old_value, new_value| 
    end 
end 

¿Hay un estándar o complemento Rails para esto? Siento que debe estar ahí en algún lado y solo me lo estoy perdiendo.

Respuesta

18

La respuesta de cwninja debería hacer el truco, pero hay un poco más.

En primer lugar, el manejo de los atributos de base se realiza con el método write_attribute, por lo que debe aprovechar esta opción.

Rails también tiene una estructura de devolución de llamada incorporada que podría ser útil si bien no permite pasar argumentos, lo cual es un poco molesto.

Uso de devoluciones de llamada personalizados que podrían hacerlo de esta manera:

class Person < ActiveRecord::Base 

    def write_attribute(attr_name, value) 
    attribute_changed(attr_name, read_attribute(attr_name), value) 
    super 
    end 

    private 

    def attribute_changed(attr, old_val, new_val) 
     logger.info "Attribute Changed: #{attr} from #{old_val} to #{new_val}" 
    end 

end 

Si quisiera tratar de utilizar Rails devoluciones de llamada (especialmente útil si usted podría tener múltiples devoluciones de llamada y/o subclases) que podría hacer algo como esto :

class Person < ActiveRecord::Base 
    define_callbacks :attribute_changed 

    attribute_changed :notify_of_attribute_change 

    def write_attribute(attr_name, value) 
    returning(super) do 
     @last_changed_attr = attr_name 
     run_callbacks(:attribute_changed) 
    end 
    end 

    private 

    def notify_of_attribute_change 
     attr = @last_changed_attr 
     old_val, new_val = send("#{attr}_change") 
     logger.info "Attribute Changed: #{attr} from #{old_val} to #{new_val}" 
    end 

end 
+0

Peter, eso es hermoso, va a ahorrarme un TONELADA de "write_attribute". He ensuciado todo mi código. ¡Gracias! – BushyMark

+0

Parece que esto es solo para Rails 2. – lulalala

+0

Llegó a este a través de google sin fecha de verificación. Este tutorial más reciente hizo lo que necesitaba: [Cómo controlar los cambios de un modelo en un 'after_callbacks'] (http://ruby-journal.com/how-to-track-changes-with-after-callbacks-in- rails-3-o-newer /) – Jay

5

intento:

def attribute_changed(attribute_name, old_value, new_value) 
end 

def attribute=(attribute_name, value) 
    returning(super) do 
    attribute_changed(attribute_name, attribute_was(attribute_name), attribute(attribute_name)) 
    end 
end 

acabo de inventar esto ahora, pero debería funcionar.

+0

Gracias por ponerme en el camino correcto. – Mario

3

siempre se puede acceder al método privado changed_attributes y comprobar las claves no utilizar un before_save y hacer con ella lo que quiera.

14

para los carriles 3:

class MyModel < ActiveRecord::Base 
    after_update :my_listener, :if => :my_attribute_changed? 

    def my_listener 
    puts "Attribute 'my_attribute' has changed" 
    end 
end 

comentó en: https://stackoverflow.com/a/1709956/316700

no encuentro la documentación oficial sobre esto.

+2

Creo que quiere que se invoque un método tan pronto como se cambie un atributo en la memoria, antes de llamar a guardar, es decir, 'my_object.some_atter =" foo "' generaría una devolución de llamada. – MikeJ

+0

'after_update' está en desuso desde los carriles 2.3 (http://apidock.com/rails/ActiveRecord/Callbacks/after_update) – schmijos

2

La manera más fácil que he encontrado:

after_save :my_method, :if => Proc.new{ self.my_attribute_changed? } 

def my_method 
    ... do stuff... 
end 
+1

after_save: my_method,: if =>: my_attribute_changed? –

+0

¡agradable! es aún mejor :) – pesta

8

para los carriles 4

def attribute=(value) 
    super(value) 
    your_callback(self.attribute) 
end 

Si desea sobrescribir el valor del atributo se debe utilizar write_attribute(:attribute, your_callback(self.attribute)) y no attribute= o te bucle sobre hasta que obtienes una excepción de pila demasiado profunda.

Cuestiones relacionadas