58 votes

after_commit pour un attribut

J'utilise un after_commit dans mon application.

Je voudrais qu'il se déclenche uniquement lorsqu'un champ particulier est mis à jour dans mon modèle. Quelqu'un sait-il comment faire ?

78voto

d_ethier Points 1826

Vieille question, mais voici une méthode que j'ai trouvée et qui pourrait fonctionner avec le callback after_commit (à partir de Réponse de paukul ). Au moins, les valeurs persistent toutes deux après la validation dans l'IRB.

after_commit :callback, 
  if: proc { |record| 
    record.previous_changes.key?(:attribute) &&
      record.previous_changes[:attribute].first != record.previous_changes[:attribute].last
  }

1 votes

Sur la base de la documentation Je pense que keys doit être appelé : record.previous_changes.keys.include?(:attribute) . Avec cette modification, cela fonctionne pour moi.

17 votes

record.previous_changes.key?(:attribute) est une condition suffisante, car la première et la dernière valeur ne sont jamais égales.

3 votes

@jamesdevar a raison, mais cette solution peut échouer si votre modèle est sauvegardé plus d'une fois pendant la transaction. Voici un exemple de projet Rails github.com/ccmcbeck/after-commit pour démontrer le problème et la solution que j'ai trouvée pour le résoudre.

23voto

paukul Points 1856

Répondre à cette vieille question parce qu'elle apparaît encore dans les résultats de recherche

vous pouvez utiliser le changements_précédents qui renvoie un hachage du format :

{ "changed_attribute" => ["old value", "new value"] }

c'est ce que changements jusqu'à ce que l'enregistrement soit réellement sauvegardé (à partir de active_record/attribute_methods/dirty.rb) :

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

Dans votre cas, vous pouvez donc vérifier previous_changes.key? "your_attribute" ou quelque chose comme ça

8voto

Paul.s Points 23073

Je ne pense pas que vous puissiez le faire en after_commit

La fonction after_commit est appelée après que la transaction a été validée. Transactions Rails

Par exemple, dans ma console rails

> 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 

Par conséquent, vous ne pourrez pas utiliser la fonction :if condition sur after_commit car l'attribut ne sera plus marqué comme modifié puisqu'il a été enregistré. Il se peut que vous deviez déterminer si le champ que vous recherchez est changed? dans un autre callback avant que l'enregistrement ne soit sauvegardé ?

8voto

Chris Beck Points 190

Il s'agit d'un problème très ancien, mais l'on s'accorde à dire qu'il n'a pas été résolu. previous_changes n'est tout simplement pas assez robuste. Dans un ActiveRecord Il y a de nombreuses raisons pour lesquelles vous pouvez sauver un modèle deux fois. previous_changes ne reflète que le résultat de la dernière save . Prenons l'exemple suivant

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

qui produit :

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

mais ce qu'il faut, c'est.. :

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

Ma solution est donc la suivante :

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 vous pouvez essayer ici : https://github.com/ccmcbeck/after-commit

6voto

sledge_909 Points 319

Vieille question, mais toujours présente dans les résultats de recherche.

À partir de Rails 5 attribute_changed? a été supprimée. L'utilisation de saved_change_to_attribute? au lieu de attribute_changed? est recommandée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X