2011-08-19 8 views
40

Estoy usando un after_commit en mi aplicación.after_commit para un atributo

Me gustaría que se active solo cuando se actualiza un campo en particular en mi modelo. ¿Alguien sabe cómo hacer eso?

Respuesta

3

Parece que quiere algo como un conditional callback. Si usted había publicado algún código te podría haber apuntado en la dirección correcta sin embargo, creo que se quiere usar algo como esto:

after_commit :callback, 
    :if => Proc.new { |record| record.field_modified? } 
+0

¿está modificado el campo? un método prefabricado o este es un método que tendré que escribir para verificar el valor del campo? Básicamente, estoy tratando de recalcular y guardar la calificación de un usuario en base a todas sus calificaciones de citas. una calificación para una cita se puede agregar después de que se haya creado, entonces me gustaría saber si el campo de calificación se actualizó aquí está algo del código estoy trabajando con clase Cita alik

+0

Nop comprobar el valor del campo. –

4

no creo que pueda hacerlo en after_commit

El after_commit se llama después de la transacción se compromete Rails Transactions

Por ejemplo, en mi consola 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 posible que necesite rastrear si el campo que está buscando es changed? en otra devolución de llamada antes de guardar el registro?

19

Para responder a esta cuestión de edad, ya que todavía aparece en los resultados de búsqueda

puede utilizar el método previous_changes cuales returnes un hash del formato:

{ "changed_attribute" => [ "valor antiguo ", "nuevo valor"]}

es lo changes fue hasta que el registro se guarda realmente (de active_record/attribute_methods/dirty.rb):

def save(*) #:nodoc: 
    if status = super 
     @previously_changed = changes 
     @changed_attributes.clear 
     # .... whatever goes here 

por lo que en su caso puede comprobar para previous_changes.key? "your_attribute" o algo por el estilo

63

pregunta antiguo, pero esto es un método que he encontrado que podría trabajar con la devolución de llamada after_commit (trabajando fuera paukul's answer). 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 
    } 
+1

Según [documentación] (http://apidock.com/rails/ActiveModel/Dirty/previous_changes), creo que 'keys' debe llamarse:' record.previous_changes.keys.include? (: Attribute) '. Con este cambio, funciona para mí. – milkfarm

+13

'record.previous_changes.key? (: Attribute)' es condición suficiente, porque el primer y el último valor no siempre son iguales. –

+2

@jamesdevar tiene razón, pero esta solución puede fallar si su modelo se guarda más de una vez durante la transacción. Aquí hay un ejemplo del proyecto de Rails https://github.com/ccmcbeck/after-commit para demostrar el problema y la solución para solucionarlo. –

0

Use gem ArTransactionChanges. previous_changes no está funcionando 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 
1

Este es un problema muy antiguo, pero la solución aceptada previous_changes simplemente no es lo suficientemente robusta. En una transacción ActiveRecord, hay muchas razones por las que puede guardar un Modelo dos veces. previous_changes solo refleja el resultado del save final.Considere 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 

que da salida:

{"title"=>["1", "2"], "updated_at"=>[...]} 

pero, lo que necesita es:

{"title"=>["1", "2"], "number"=>[1, 2], "updated_at"=>[...]} 

Por lo tanto, mi solución es la siguiente:

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://stackoverflow.com/a/20955038 
    def track_saved_value(v) 
    begin 
     v.dup 
    rescue TypeError 
     v 
    end 
    end 
end 

que se puede probar aquí: https://github.com/ccmcbeck/after-commit

Cuestiones relacionadas