¿Determinar qué atributos se cambiaron en la devolución de llamada after_save de Rails?

153

Estoy configurando una devolución de llamada after_save en mi observador modelo para enviar una notificación solo si el atributo publicado del modelo se cambió de falso a verdadero. ¿Desde métodos como los cambiados? solo son útiles antes de guardar el modelo, la forma en que actualmente (y sin éxito) intento 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 usando devoluciones de llamada de observador modelo (para no contaminar mi código de controlador)?

modulaaron
fuente

Respuestas:

182

Rieles 5.1+

Uso saved_change_to_published?:

class SomeModel < ActiveRecord::Base
  after_update :send_notification_after_change

  def send_notification_after_change
    Notification.send(…) if (saved_change_to_published? && self.published == true)
  end

end

O si lo prefiere, saved_change_to_attribute?(:published).

Rieles 3–5.1

Advertencia

Este enfoque funciona a través de Rails 5.1 (pero está en desuso en 5.1 y tiene cambios importantes en 5.2). Puede leer sobre el cambio en esta solicitud de extracción .

En su after_updatefiltro en el modelo puede usar el _changed?descriptor de acceso. 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

Radek Paviensky
fuente
Olvídese de lo que dije anteriormente: NO funciona en Rails 2.0.5. Una adición útil a Rails 3.
stephenr
44
Creo que after_update está en desuso ahora? De todos modos, intenté esto en un gancho after_save y parecía funcionar bien. (El hash de cambios () todavía no se ha restablecido todavía en un after_save, aparentemente.)
Tyler Rick
3
Tuve que incluir esta línea en el archivo model.rb. incluye ActiveModel :: Dirty
coderVishal
13
En versiones posteriores de Rails puede agregar la condición a la after_updatellamada:after_update :send_notification_after_change, if: -> { published_changed? }
Koen.
11
Habrá algunos cambios de API en Rails 5.2. Tendrá que hacer saved_change_to_published?o saved_change_to_publishedbuscar el cambio durante la devolución de llamada
alopez02
183

Para aquellos que desean conocer los cambios que se acaban de realizar en una after_savedevolución de llamada:

Rieles 5.1 y superiores

model.saved_changes

Rieles <5.1

model.previous_changes

Ver también: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes

Jacek Głodek
fuente
2
Esto funciona perfectamente cuando no desea utilizar devoluciones de llamada modelo y necesita un guardado válido antes de llevar a cabo más funciones.
Dave Robertson el
¡Esto es perfecto! Gracias por publicar esto.
jrhicks
¡Increíble! Gracias por esto.
Daniel Logan
44
Para que quede claro: en mis pruebas (Rails 4), si está utilizando una after_savedevolución de llamada, self.changed?es truey self.attribute_name_changed?es también true, pero self.previous_changesdevuelve un hash vacío.
sandre89
9
Esto está en desuso en Rails 5.1 +. Usar saved_changesen after_savedevoluciones de llamada en su lugar
rico_mac
71

Para cualquiera que vea esto más adelante, ya que actualmente (agosto de 2017) encabeza Google: Vale la pena mencionar que este comportamiento se alterará en Rails 5.2 y tiene advertencias de desaprobación a partir de Rails 5.1, ya que ActiveModel :: Dirty cambió un poco .

Que cambio?

Si está utilizando el attribute_changed?método en after_*-callbacks, verá una advertencia como:

ADVERTENCIA DE DEPRECACIÓN: El comportamiento del attribute_changed?interior de las devoluciones de llamada posteriores 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 savedevolverlo (por ejemplo, lo contrario de lo que devuelve ahora). Para mantener el comportamiento actual, use saved_change_to_attribute?en su lugar. (llamado desde some_callback en /PATH_TO/app/models/user.rb:15)

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

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

ADVERTENCIA DE DEPRECACIÓN: El comportamiento del attribute_changeinterior de las devoluciones de llamada posteriores 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 savedevolverlo (por ejemplo, lo contrario de lo que devuelve ahora). Para mantener el comportamiento actual, use saved_change_to_attributeen su lugar. (llamado desde some_callback en /PATH_TO/app/models/user.rb:20)

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

Frederik Spang
fuente
2
Tenga en cuenta que esta útil respuesta también incluye soluciones alternativas para la depreciación de los attribute_wasmétodos: use saved_change_to_attributeen su lugar.
Joe Atzberger
47

En caso de que pueda hacer esto en before_savelugar de after_save, podrá 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 resultados anteriores y posteriores como matrices

zeacuss
fuente
8
Excepto que estos no funcionan en una after_devolución de llamada, que es de lo que realmente se trataba la pregunta. La respuesta de @ jacek-głodek a continuación es la correcta.
Jazz
Se actualizó la respuesta para dejar en claro que esto solo se aplica abefore_save
mahemoff
1
¿Qué versión de Rails es esta? En Rails 4, self.changedse puede usar en after_savedevoluciones de llamada.
sandre89
¡Funciona genial! También debe tenerse en cuenta, ¡el resultado de self.changedes una serie de cadenas! (¡No símbolos!)["attr_name", "other_attr_name"]
Lucas
8

La respuesta "seleccionada" no funcionó para mí. Estoy usando rails 3.1 con CouchRest :: Model (basado en Active Model). Los _changed?métodos no devuelven verdadero para los atributos modificados en el after_updategancho, solo en el before_updategancho. Pude hacer que funcionara con el around_updategancho (¿nuevo?) :

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
Jeff Gran
fuente
1
La respuesta seleccionada tampoco funcionó para mí, pero esto sí. ¡Gracias! Estoy usando ActiveRecord 3.2.16.
Ben Lee
5

puede agregar una condición after_updatesimilar para que así:

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

  ...
end

no es necesario agregar una condición dentro del send_notificationmétodo mismo.

eco
fuente
-17

Simplemente agrega 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
shingara
fuente
Esta es una muy mala práctica, ni siquiera lo consideres.
Nuno Silva