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


Estoy configurando una devolución de llamada after_save en mi observador de modelo para enviar una notificación solo si el atributo published del modelo se cambió de falso a verdadero. Desde métodos como cambiado? solo son útiles antes de que se guarde 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 callbacks de model observer (para no contaminar mi controlador código)?

Author: Frederik Spang, 2010-10-05

8 answers

En su filtro after_update en el modelo, puede usar _changed? accessor (al menos en Rails 3, no 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.

 157
Author: Radek Paviensky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-05 08:09:06

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

model.previous_changes

Esto funciona como model.change pero funciona todavía después de model.save, etc. Encontré esta información útil, así que tal vez usted también lo haga.

En Rails 5.1 + esto está obsoleto. Use saved_changes en after_save callbacks en su lugar.

 129
Author: Jacek Głodek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-18 09:47:55

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

self.changed

Devuelve una matriz de todas las columnas cambiadas 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

 45
Author: zeacuss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-04 12:59:51

A cualquiera que vea esto más tarde, como actualmente (Ago. 2017) tops google: Vale la pena mencionar que este comportamiento se alterará en Rails 5.2 , y tiene advertencias de obsolescencia a partir de Rails 5.1, ya que ActiveModel::Dirty cambió un poco.

¿Qué cambio?

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

ADVERTENCIA DE DESAPROBACIÓN: El comportamiento de attribute_changed? dentro de after callbacks será cambiando en la próxima versión de Rails. El nuevo valor devuelto reflejará el comportamiento de llamar al método después de save devuelto (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, podría arreglar esto fácilmente reemplazando la función con saved_change_to_attribute?. Así, por ejemplo, name_changed? se convierte en saved_change_to_name?.

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

ADVERTENCIA DE OBSOLESCENCIA: El comportamiento de attribute_change dentro de after callbacks cambiará en la próxima versión de Rails. El nuevo valor devuelto reflejará el comportamiento de llamar al método después de save devuelto (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: 20)

De nuevo, 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 estos se pueden acceder como saved_changes['attribute'].

 25
Author: Frederik Spang,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-03 19:45:24

La respuesta "seleccionada" no funcionó para mí. Estoy usando rails 3.1 con CouchRest:: Model (basado en el Modelo Activo). Los métodos _changed? no devuelven true para los atributos modificados en el gancho after_update, solo en el gancho before_update. Pude hacerlo funcionar usando el (¿nuevo?) around_update hook:

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
 6
Author: Jeff Gran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-02-10 19:53:32

Puedes añadir una condición a la after_update así:

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

  ...
end

No hay necesidad de agregar una condición dentro del propio método send_notification.

 2
Author: echo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-15 18:18:23

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}

El

attr[1].last == false

Es necesario cuando el nuevo valor es false, donde la asignación devuelve false y no se devuelve "hash".

Supongo que hay una manera más fácil, soy nuevo en rails

 0
Author: Maragues,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-14 14:59:55

Solo tienes que añadir un accessor que defina lo que cambias

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
 -14
Author: shingara,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-05 08:08:29