¿Cómo detectar cambios en los atributos del modelo?

85

Me gustaría crear una función de devolución de llamada en rieles que se ejecute después de guardar un modelo.

Tengo este modelo, Reclamo que tiene un atributo 'estado' el cual cambia dependiendo del estado del reclamo, posibles valores están pendientes, avalados, aprobados, rechazados

La base de datos tiene 'estado' con el valor predeterminado de 'pendiente'.

Me gustaría realizar ciertas tareas después de que el modelo se crea por primera vez o se actualiza de un estado a otro, según el estado desde el que cambia.

Mi idea es tener una función en el modelo:

    after_save :check_state

    def check_state
      # if status changed from nil to pending (created)
      do this

      # if status changed from pending to approved
      performthistask
     end

Mi pregunta es ¿cómo verifico el valor anterior antes del cambio dentro del modelo?

David C
fuente

Respuestas:

173

Debería mirar el módulo ActiveModel :: Dirty : Debería poder realizar las siguientes acciones en su modelo Claim:

claim.status_changed?  # returns true if 'status' attribute has changed
claim.status_was       # returns the previous value of 'status' attribute
claim.status_change    # => ['old value', 'new value'] returns the old and 
                       # new value for 'status' attribute

claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}

Oh! las alegrías de Rails!

Harish Shetty
fuente
6
Esto no funcionará después de que se guarde el modelo, que es lo que pidió.
Tom Rossi
4
@TomRossi, las dirtyllamadas funcionan en after_save(tanto en Rails 2.3 como 3.x). Lo he usado varias veces.
Harish Shetty
11
@TomRossi, los indicadores sucios se restablecen después de la confirmación, por lo que no estarán disponibles en las after_commitdevoluciones de llamada introducidas en Rails 3.x. Ciertamente funcionarán after_save.
Harish Shetty
¡No tenía ni idea! ¡Pensé que se restablecieron una vez que se guardó!
Tom Rossi
5
@TomRossi Comencé con la misma suposición hace unos años. Cuando intenté verificar las banderas sucias en after_save, funcionó. En esencia, after_savees una devolución de llamada para un estado entre after DMLy before_commit. Puede terminar toda la transacción after_savelanzando una excepción. Si desea hacer algo después de guardar sin afectar la operación actual, use after_commit:-)
Harish Shetty
38

puedes usar esto

self.changed

devuelve una matriz de todas las columnas que cambiaron 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

Zecuss
fuente
7
Solo una nota menor para decir que no es necesario usar self.en estos, solo puede decir changedy changes.
user664833
@ user664833 Más específicamente, puede omitir selfen el modelo en sí, pero puede llamarlos en cualquier objeto con object.changedy object.changes. :)
Joshua Pinter
4

Te recomiendo que eches un vistazo a uno de los complementos de máquina de estado disponibles:

Cualquiera de los dos le permitirá configurar estados y transiciones entre estados. Una forma muy útil y sencilla de gestionar sus necesidades.

Toby Hede
fuente
Estoy probando rubyist-aasm. Digamos que tengo clase Claim <ActiveRecord :: Base include AASM aasm_column: status aasm_initial_state: pendiente aasm_state: pendiente,: enter =>: enter_pending def enter_pending Notifier.deliver_pending_notification (self) end end Y mi campo de estado en mi base de datos tiene el valor predeterminado de "pendiente". Si tuviera que hacer un Claim.create sin completar el campo de estado (para que se ejecute como 'pendiente'), ¿AASM ejecutará el método 'enter_pending'?
David C
2

Para Rails 5.1+, debe usar el método de atributo de registro activo: Saved_change_to_attribute?

Saved_change_to_attribute? (attr_name, ** opciones) `

¿Cambió este atributo la última vez que guardamos? Este método se puede invocar como en saved_change_to_name?lugar de saved_change_to_attribute?("name"). Se comporta de manera similar a attribute_changed?. Este método es útil en las devoluciones de llamada posteriores para determinar si la llamada a guardar cambió un determinado atributo.

Opciones

from Cuando se pasa, este método devolverá falso a menos que el valor original sea igual a la opción dada

to Cuando se pasa, este método devolverá falso a menos que el valor se cambie al valor dado

Entonces, su modelo se verá así, si desea llamar a algún método basado en el cambio en el valor del atributo:

class Claim < ApplicationRecord
  
  after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }

  after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }

  
  def do_this
    ..
    ..
  end

  def do_that
    ..
    ..
  end

end

Y si no desea verificar el cambio de valor en la devolución de llamada, puede hacer lo siguiente:

class Claim < ApplicationRecord

  after_save: :do_this, if: saved_change_to_status?


  def do_this
    ..
    ..
  end

end
Rajkaran Mishra
fuente
0

He visto surgir la pregunta en muchos lugares, así que escribí un pequeño rubygem para hacer el código un poco más agradable (y evitar un millón de declaraciones if / else en todas partes): https://github.com/ronna-s / on_change . Espero que eso ayude.

Ronna
fuente
0

Estará mucho mejor si utiliza una solución bien probada como la gema state_machine .

Paz Aricha
fuente