Determine qué atributos se cambiaron en Rails after_save callback?

Estoy configurando una callback after_save en mi observador modelo para enviar una notificación solo si el atributo publicado del modelo se cambió de falso a verdadero. Dado que los metodos como los modificados? solo son útiles antes de que se guarde el modelo, la forma en la que actualmente (y sin éxito) estoy intentando hacerlo es la siguiente:

def before_save(blog) @og_published = blog.published? end def after_save(blog) if @og_published == false and blog.published? == true Notification.send(...) end end 

¿Alguien tiene alguna sugerencia sobre la mejor manera de manejar esto, preferiblemente utilizando devoluciones de llamada del observador modelo (para no contaminar el código de mi controlador)?

En su filtro after_update en el modelo puede usar _changed? Accessor (al menos en Rails 3, no estoy seguro para Rails 2). Así por ejemplo:

 class SomeModel < ActiveRecord::Base after_update :send_notification_after_change def send_notification_after_change Notification.send(...) if (self.published_changed? && self.published == true) end end 

Simplemente funciona.

Para aquellos que quieran conocer los cambios después de guardar, debes usar

 model.previous_changes 

esto funciona como model.change pero sigue funcionando después de model.save , etc. Encontré esta información útil, así que quizás usted también lo haga.

En Rails 5.1+ esto está en desuso. Utilice saved_changes en after_save callbacks en su lugar.

En caso de que puedas hacer esto en before_save lugar de after_save , podrás usar esto:

 self.changed 

devuelve una matriz de todas las columnas modificadas en este registro.

también puedes usar:

 self.changes 

que devuelve un hash de columnas que cambiaron y antes y después de los resultados como matrices

A cualquiera que vea esto más adelante, ya que actualmente (agosto de 2017) encabeza google: vale la pena mencionar que este comportamiento se modificará en Rails 5.2 y tiene advertencias de rechazo a partir de Rails 5.1, ya que ActiveModel :: Dirty cambió un poco .

¿Qué cambio?

Si estás usando attribute_changed? En el método after_* -callbacks, verá una advertencia como:

ADVERTENCIA DE DEPRECACIÓN: ¿El comportamiento de attribute_changed? dentro de después de las devoluciones de llamada cambiará en la próxima versión de Rails. El nuevo valor de retorno reflejará el comportamiento de llamar al método después de que se devuelva save (por ejemplo, lo contrario de lo que devuelve ahora). Para mantener el comportamiento actual, use saved_change_to_attribute? en lugar. (se llama desde some_callback en /PATH_TO/app/models/user.rb:15)

Como se menciona, ¿podría solucionar esto fácilmente reemplazando la función con saved_change_to_attribute? . Entonces, por ejemplo, name_changed? se convierte en saved_change_to_name? .

Del mismo modo, si está utilizando el attribute_change para obtener los valores de antes-después, esto también cambia y arroja lo siguiente:

ADVERTENCIA DE DEPRECACIÓN: El comportamiento de attribute_change dentro de después de las devoluciones de llamada cambiará en la próxima versión de Rails. El nuevo valor de retorno reflejará el comportamiento de llamar al método después de que se devuelva save (por ejemplo, lo contrario de lo que devuelve ahora). Para mantener el comportamiento actual, use saved_change_to_attribute en saved_change_to_attribute lugar. (se llama desde some_callback en /PATH_TO/app/models/user.rb:20)

Nuevamente, como se menciona, el método cambia el nombre a saved_change_to_attribute que devuelve ["old", "new"] . o use saved_changes , que devuelve todos los cambios, y se puede acceder a ellos como saved_changes['attribute'] .

La respuesta “seleccionada” no funcionó para mí. Estoy usando Rails 3.1 con CouchRest :: Model (basado en Active Model). El _changed? los métodos no devuelven verdadero para los atributos cambiados en el gancho after_update , solo en el gancho before_update . Pude hacer que funcionara con el (¿nuevo?) Gancho around_update :

 class SomeModel < ActiveRecord::Base around_update :send_notification_after_change def send_notification_after_change should_send_it = self.published_changed? && self.published == true yield Notification.send(...) if should_send_it end end 

puede agregar una condición al after_update así:

 class SomeModel < ActiveRecord::Base after_update :send_notification, if: :published_changed? ... end 

no es necesario agregar una condición dentro del send_notification método send_notification .

Estoy usando esto para extraer un hash con los nuevos valores de atributo, lo que me resulta útil para actualizar otros modelos.

 attributes_changed = self.changes.inject(Hash.new){|hash,attr| ((hash[attr[0].to_sym] = attr[1].last) || attr[1].last == false) && hash} 

los

 attr[1].last == false 

es necesario cuando el nuevo valor es false , donde la asignación devuelve falso y “hash” no se devuelve.

Supongo que hay una manera más fácil, soy nuevo en los Rails.

Usted acaba de agregar un descriptor de acceso que define lo que cambia

 class Post < AR::Base attr_reader :what_changed before_filter :what_changed? def what_changed? @what_changed = changes || [] end after_filter :action_on_changes def action_on_changes @what_changed.each do |change| p change end end end