Usando ActiveRecord, ¿hay alguna manera de obtener los valores antiguos de un registro durante after_update?

81

Configuración usando un ejemplo simple: tengo 1 tabla ( Totals) que contiene la suma de la amountcolumna de cada registro en una segunda tabla ( Things).

Cuando thing.amountse actualiza, me gustaría simplemente agregar la diferencia entre el valor anterior y el nuevo valor a total.sum.

Ahora mismo estoy restando self.amountdurante before_updatey sumando self.amountdurante after_update. Esto deposita demasiada confianza en que la actualización se realice correctamente.

Restricción: no quiero simplemente volver a calcular la suma de todas las transacciones.

Pregunta: Simplemente, me gustaría acceder al valor original durante una after_updatedevolución de llamada. ¿Qué formas se te han ocurrido para hacer esto?

Actualización: voy con la idea de Luke Francl. Durante una after_updatedevolución de llamada, todavía tiene acceso a los self.attr_wasvalores, que es exactamente lo que quería. También decidí ir con una after_updateimplementación porque quiero mantener este tipo de lógica en el modelo. De esta manera, no importa cómo decida actualizar las transacciones en el futuro, sabré que estoy actualizando la suma de las transacciones correctamente. Gracias a todos por sus sugerencias de implementación.

Abel
fuente

Respuestas:

148

Lo mismo ocurre con lo que todos dicen sobre las transacciones.

Dicho eso ...

ActiveRecord a partir de Rails 2.1 realiza un seguimiento de los valores de los atributos de un objeto. Entonces, si tiene un atributo total, tendrá un total_changed?método y un total_wasmétodo que devuelve el valor anterior.

Ya no es necesario agregar nada a su modelo para realizar un seguimiento de esto.

Actualización: aquí está la documentación para ActiveModel :: Dirty según lo solicitado.

Luke Francl
fuente
Pensé que existía algo como esto. ¿Puede vincular a la documentación relevante sobre attr_changed? y attr_was?
Gabe Hollombe
Claro, agregué un enlace a la respuesta.
Luke Francl
10

Algunas otras personas están mencionando envolver todo esto en una transacción, pero creo que eso está hecho para usted; solo necesita activar la reversión generando una excepción para errores en las devoluciones de llamada after_ *.

Ver http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Toda la cadena de devolución de llamada de una llamada de guardar, guardar o destruir se ejecuta dentro de una transacción. Eso incluye after_ * hooks. Si todo va bien, se ejecuta COMMIT una vez que se ha completado la cadena.

Si una devolución de llamada before_ * cancela la acción, se emite una ROLLBACK. También puede activar un ROLLBACK generando una excepción en cualquiera de las devoluciones de llamada, incluidos los ganchos after_ *. Sin embargo, tenga en cuenta que, en ese caso, el cliente debe ser consciente de ello porque un guardado normal generará dicha excepción en lugar de devolver silenciosamente falso.

Gabe Hollombe
fuente
10

Agregar "_was" a su atributo le dará el valor anterior antes de guardar los datos.

Estos métodos se denominan métodos sucios .

¡Salud!

Manish Shrivastava
fuente
8

Para obtener todos los campos modificados, con sus valores nuevos y antiguos, respectivamente:

person = Person.create!(:name => 'Bill')
person.name = 'Bob'
person.save
person.changes        # => {"name" => ["Bill", "Bob"]}
thomax
fuente
5
Creo que la última línea debería leerse ahora en person.previous_changeslugar deperson.changes
Jason Axelson
5

ActiveRecord :: Dirty es un módulo integrado en ActiveRecord para realizar un seguimiento de los cambios de atributos. Entonces puedes usar thing.amount_waspara obtener el valor anterior.

John Topley
fuente
3

Agregue esto a su modelo:

def amount=(new_value)
    @old_amount = read_attribute(:amount)
    write_attribute(:amount,new_value)
end

Luego use @old_amount en su código after_update.

Daniel Von Fange
fuente
0

En primer lugar, debe hacer esto en una transacción para asegurarse de que sus datos se escriban juntos.

Para responder a su pregunta, puede establecer una variable miembro en el valor anterior en before_update, al que luego puede acceder en after_update, sin embargo, esta no es una solución muy elegante.

Jonnii
fuente
Estoy luchando por ver cómo implementaría lo que quiero usando una transacción. ¿Una transacción como esta vive en el modelo o en el controlador? ¿Elimino mis devoluciones de llamada 'after_update' y 'before_update'? Finalmente, ¿cómo obtengo el valor anterior que necesito para determinar la diferencia?
Abel
no importa, veo que tengo que colocar el código en el controlador. estamos bien.
Abel
0

Idea 1: Envuelva la actualización en una transacción de base de datos, de modo que si la actualización falla, su tabla de Totales no cambie: Documentos de Transacciones de ActiveRecord

Idea 2: guarde el valor anterior en @old_total durante el before_update.

Bradheintz
fuente
0

Si desea obtener el valor de un campo en particular después de la actualización, puede usar el método field_before_last_save.

Example:

u = User.last
u.update(name: "abc")

u.name_before_last_save will give you old value (before update value)
Jigar Bhatt
fuente