Cómo determinar si un registro se acaba de crear o actualizar en after_save

95

¿El #new_record? La función determina si se ha guardado un registro. Pero siempre es falso en el after_savegancho. ¿Hay alguna forma de determinar si el registro es un registro recién creado o uno antiguo de actualización?

Espero no usar otra devolución de llamada, como before_createestablecer una bandera en el modelo o requerir otra consulta en la base de datos.

Se agradece cualquier consejo.

Editar: Necesito determinarlo en el after_savegancho, y para mi caso de uso particular, no hay una marca de tiempo updated_atoupdated_on

Aaron Qian
fuente
1
hmm tal vez pasar un parámetro en un before_save? pensando en voz alta
Viaje

Respuestas:

167

Estaba buscando usar esto para una after_savedevolución de llamada.

Una solución más sencilla es usar id_changed?(ya que no cambiará update) o incluso created_at_changed?si hay columnas de marca de tiempo.

Actualización: como señala @mitsy, si esta verificación es necesaria fuera de las devoluciones de llamada, use id_previously_changed?. Ver documentos .

Zubin
fuente
3
vía ActiveModel :: Dirty
chaserx
6
Es mejor diferenciar con after_update y after_create. Las devoluciones de llamada pueden compartir un método común que toma un argumento para indicar si es una creación o una actualización.
matthuhiggins
2
Esto podría haber cambiado. Al menos en Rails 4, la devolución de llamada after_save se ejecuta después de la devolución de llamada after_create o after_update (consulte guides.rubyonrails.org/active_record_callbacks.html ).
Mark
3
Marcando ninguno de estos campos funciona fuera de after_save.
fatuhoku
3
id_changed?será falso después de que se guarde el registro (al menos fuera de los ganchos). En ese caso, puede utilizarid_previously_changed?
mltsy
30

No hay magia de rieles aquí que yo sepa, tendrás que hacerlo tú mismo. Podrías limpiar esto usando un atributo virtual ...

En tu clase de modelo:

def before_save
  @was_a_new_record = new_record?
  return true
end

def after_save
  if @was_a_new_record
    ...
  end
end
Jeff Paquette
fuente
26

Sin embargo, otra opción, para aquellos que hacen tiene una updated_atmarca de tiempo:

if created_at == updated_at
  # it's a newly created record
end
colllin
fuente
No es una mala idea, pero parece que esto podría ser contraproducente en algunas situaciones (no necesariamente a prueba de balas).
Ash Blue
Dependiendo de cuándo se ejecute una migración, los registros created_at y updated_at podrían estar desactivados. Además, siempre existe la posibilidad de que alguien actualice un registro justo después de guardarlo inicialmente, lo que podría hacer que el tiempo no esté sincronizado. No es una mala idea, solo parece que se podría agregar una implementación más a prueba de balas.
Ash Blue
Además, pueden ser iguales mucho después de que se haya creado inicialmente el registro. Si un registro no se actualiza durante meses, parecerá que se acaba de crear .
bschaeffer
1
@bschaeffer Lo siento, mi pregunta fue "¿es posible created_atigualar updated_aten una after_savedevolución de llamada en cualquier momento que no sea cuando se creó por primera vez?"
colllin
1
@colllin: al crear un registro, created_aty updated_atserá igual en una after_savedevolución de llamada. En todas las demás situaciones, no serán iguales en la after_savedevolución de llamada.
bschaeffer
22

Hay una after_createdevolución de llamada a la que solo se llama si el registro es un registro nuevo, después de guardarlo . También hay una after_updatedevolución de llamada para usar si se trataba de un registro existente que se cambió y se guardó. La after_savedevolución de llamada se llama en ambos casos, después de que se llame after_createo after_update.

Úselo after_createsi necesita que algo suceda una vez después de que se haya guardado un nuevo registro.

Más información aquí: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Sharpone
fuente
1
Oye, gracias por la respuesta, esto me ayudó mucho. Cheers man, +10 :)
Adam McArthur
1
En realidad, esto podría no ser cierto. Si tiene asociaciones en su creación, after_create se llama ANTES de que se creen las asociaciones, por lo que si necesita asegurarse de que TODO se haya creado, debe usar after_save
Niels Kristian
18

Dado que el objeto ya se ha guardado, debería consultar los cambios anteriores. La ID solo debe cambiar después de crear.

# true if this is a new record
@object.previous_changes[:id].any?

También hay una variable de instancia @new_record_before_save. Puede acceder a eso haciendo lo siguiente:

# true if this is a new record
@object.instance_variable_get(:@new_record_before_save)

Ambos son bastante feos, pero te permitirían saber si el objeto se ha creado recientemente. ¡Espero que ayude!

Tom Rossi
fuente
Actualmente estoy en byebug en una after_savedevolución de llamada usando Rails 4 y ninguno de estos está funcionando para identificar este nuevo registro.
MCB
Yo diría que @object.previous_changes[:id].any?es bastante simple y elegante. Me funciona después de que se actualiza el registro (no lo llamo desde after_save).
thekingoftruth
1
@object.id_previously_changed?es un poco menos feo.
Noble
@MCB en after_saveRails 4 que le gustaría ver en changes[:id]lugar de previous_changes[:id]. Sin embargo, esto está cambiando en Rails5.1 (ver discusión en github.com/rails/rails/pull/25337 )
gmcnaughton
previous_changes.key?(:id)para una mejor comprensión.
Sebastian Palma
2

Rails 5.1+ forma:

user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true
Jacka
fuente
1

Para Rails 4 (verificado en 4.2.11.1), los resultados de changesy los previous_changesmétodos son hashes vacíos {}en la creación de objetos dentro after_save. Entonces, attribute_changed?métodos como id_changed?no funcionarán como se esperaba.

Pero puede aprovechar este conocimiento y, sabiendo que al menos 1 atributo debe estar en la changesactualización, verifique si changesestá vacío. Una vez que confirme que está vacío, debe estarlo durante la creación del objeto:

after_save do
  if changes.empty?
    # code appropriate for object creation goes here ...
  end
end
criptofero
fuente
0

Me gusta ser específico, incluso yo sé que :idno debería cambiar en la normalidad, pero

(byebug) id_change.first.nil?
true

Siempre es más barato ser específico en lugar de encontrar un error inesperado muy extraño.

De la misma manera si espero una truebandera de un argumento no confiable

def foo?(flag)
  flag == true
end

Esto ahorra muchas horas para no estar sentado sobre errores extraños.

x'ES
fuente