2011-08-19 10 views
40

Używam after_commit w mojej aplikacji.after_commit dla atrybutu

Chciałbym, aby zadziałało tylko wtedy, gdy dane pole zostanie zaktualizowane w moim modelu. Czy ktoś wie, jak to zrobić?

Odpowiedz

3

Wygląda na to, że chcesz uzyskać coś takiego jak conditional callback. Gdybyś pisał jakiś kod mógłbym wskazał w dobrym kierunku, jednak myślę, że chcesz używać coś takiego:

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

jest zmieniona w polu? gotowa metoda czy jest to metoda, którą muszę napisać, aby sprawdzić wartość pola? zasadniczo próbuję ponownie obliczyć i zapisać ocenę użytkownika na podstawie wszystkich jego ocen spotkań. Ocena do spotkania może zostać dodana po utworzeniu, więc chciałbym się dowiedzieć, czy pole oceny zostało zaktualizowane. Oto niektóre z kodu, które pracuję z klasy Powołanie alik

+0

Nope sprawdzić wartość pola. –

4

I nie sądzę, że można to zrobić w after_commit

The after_commit nazywa po transakcji jest zobowiązana Rails Transactions

na przykład w moim konsoli szyn

> 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 

dlatego nie będzie mógł użyć warunku :if na after_commit, ponieważ atrybut nie będzie już oznaczony jako zmieniony, ponieważ został zapisany. Może być konieczne sprawdzenie, czy pole, na które się znajdujesz, to changed? w innym wywołaniu zwrotnym przed zapisaniem rekordu?

19

Odpowiedź na to stare pytanie, ponieważ nadal pojawia się w wynikach wyszukiwania

można użyć metody previous_changes który returnes hash w formacie:

{ „changed_attribute” => [ "stare wartości ”, "nowa wartość"]}

to co changes dopiero rekord zostanie faktycznie zapisane (od active_record/attribute_methods/dirty.rb):

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

więc w Twoim przypadku można sprawdzić previous_changes.key? "your_attribute" czy coś takiego

63

stare pytanie, ale ta jest jedną z metod, które znalazłem, że może pracować z after_commit zwrotnego (działa od paukul's answer). Przynajmniej obie wartości utrzymują się po zatwierdzeniu w IRB.

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

Na podstawie [dokumentacja] (http://apidock.com/rails/ActiveModel/Dirty/previous_changes), myślę, że 'keys' musi być wywołana:' record.previous_changes.keys.include? (: Attribute) '. Dzięki tej zmianie działa to dla mnie. – milkfarm

+13

'record.previous_changes.key? (: Attribute)' jest wystarczającym warunkiem, ponieważ pierwsza i ostatnia wartość nie zawsze są równe. –

+2

@jamesdevar ma rację, ale to rozwiązanie może się nie udać, jeśli model zostanie zapisany więcej niż jeden raz podczas transakcji. Oto przykładowy projekt Railsów https://github.com/ccmcbeck/after-commit w celu zademonstrowania problemu i mojego rozwiązania, które go naprawi. –

0

Użyj klejnot ArTransactionChanges. previous_changes nie działa na mnie w Rails 4.0.x

Zastosowanie:

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

Jest to bardzo stary problem, ale akceptowane previous_changes rozwiązanie po prostu nie jest wystarczająco wytrzymałe. W transakcji ActiveRecord istnieje wiele powodów, dla których możesz dwukrotnie zapisać model. previous_changes odzwierciedla jedynie wynik końcowej wersji save.Rozważmy następujący przykład

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 

który Wyjścia:

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

ale, czego potrzebujesz to:

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

Więc moje rozwiązanie to:

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 

którego można wypróbować tutaj: https://github.com/ccmcbeck/after-commit

Powiązane problemy