ruby on rails - rails - after_commit para un atributo
ruby on rails tutorial (6)
Estoy usando un after_commit en mi aplicación.
Me gustaría que se active solo cuando se actualiza un campo en particular en mi modelo. ¿Alguien sabe cómo hacer eso?
Este es un problema muy antiguo, pero la solución previous_changes
aceptada simplemente no es lo suficientemente robusta. En una transacción de ActiveRecord
, hay muchas razones por las que puede guardar un Modelo dos veces. previous_changes
solo refleja el resultado del último save
. Considera este ejemplo
class Test < ActiveRecord::Base
after_commit: :after_commit_test
def :after_commit_test
puts previous_changes.inspect
end
end
test = Test.create(number: 1, title: "1")
test = Test.find(test.id) # to initialize a fresh object
test.transaction do
test.update(number: 2)
test.update(title: "2")
end
qué salidas:
{"title"=>["1", "2"], "updated_at"=>[...]}
pero, lo que necesitas es:
{"title"=>["1", "2"], "number"=>[1, 2], "updated_at"=>[...]}
Entonces, mi solución es esta:
module TrackSavedChanges
extend ActiveSupport::Concern
included do
# expose the details if consumer wants to do more
attr_reader :saved_changes_history, :saved_changes_unfiltered
after_initialize :reset_saved_changes
after_save :track_saved_changes
end
# on initalize, but useful for fine grain control
def reset_saved_changes
@saved_changes_unfiltered = {}
@saved_changes_history = []
end
# filter out any changes that result in the original value
def saved_changes
@saved_changes_unfiltered.reject { |k,v| v[0] == v[1] }
end
private
# on save
def track_saved_changes
# maintain an array of ActiveModel::Dirty.changes
@saved_changes_history << changes.dup
# accumulate the most recent changes
@saved_changes_history.last.each_pair { |k, v| track_saved_change k, v }
end
# v is an an array of [prev, current]
def track_saved_change(k, v)
if @saved_changes_unfiltered.key? k
@saved_changes_unfiltered[k][1] = track_saved_value v[1]
else
@saved_changes_unfiltered[k] = v.dup
end
end
# type safe dup inspred by http://.com/a/20955038
def track_saved_value(v)
begin
v.dup
rescue TypeError
v
end
end
end
que puedes probar aquí: github.com/ccmcbeck/after-commit
No creo que puedas hacerlo en after_commit
Se llama al after_commit después de que la transacción se haya comprometido. Rails Transactions
Por ejemplo en mi consola de rieles
> record = MyModel.find(1)
=> #<MyModel id: 1, label: "test", created_at: "2011-08-19 22:57:54", updated_at: "2011-08-19 22:57:54">
> record.label = "Changing text"
=> "Changing text"
> record.label_changed?
=> true
> record.save
=> true
> record.label_changed?
=> false
Por lo tanto, no podrá usar la condición :if
en after_commit
porque el atributo ya no se marcará como cambiado ya que se ha guardado. ¿Es necesario que rastree si el campo que busca ha changed?
en otra devolución de llamada antes de que se guarde el registro?
Parece que quieres algo así como una devolución de llamada condicional . Si hubiera publicado algún código, podría haberlo señalado en la dirección correcta; sin embargo, creo que le gustaría usar algo como esto:
after_commit :callback,
:if => Proc.new { |record| record.field_modified? }
Una vieja pregunta, pero este es un método que he encontrado que podría funcionar con la devolución de llamada after_commit (trabajando en la respuesta de paukul ). Al menos, los valores persisten después de la confirmación en IRB.
after_commit :callback,
if: proc { |record|
record.previous_changes.key?(:attribute) &&
record.previous_changes[:attribute].first != record.previous_changes[:attribute].last
}
Usa gem ArTransactionChanges . previous_changes
no funciona para mí en Rails 4.0.x
Uso:
class User < ActiveRecord::Base
include ArTransactionChanges
after_commit :print_transaction_changes
def print_transaction_changes
transaction_changed_attributes.each do |name, old_value|
puts "attribute #{name}: #{old_value.inspect} -> #{send(name).inspect}"
end
end
end
Respondiendo a esta vieja pregunta porque aún aparece en los resultados de búsqueda
puede usar el método previous_changes que devuelve un hash del formato:
{"changed_attribute" => ["valor antiguo", "valor nuevo"]}
es lo que changes hasta que el registro se guarda realmente (desde active_record / attribute_methods / dirty.rb):
def save(*) #:nodoc:
if status = super
@previously_changed = changes
@changed_attributes.clear
# .... whatever goes here
entonces en tu caso puedes verificar por previous_changes.key? "your_attribute"
previous_changes.key? "your_attribute"
o algo así