Jak wykryć zmiany atrybutów z modelu?

85

Chciałbym utworzyć funkcję zwrotną w railsach, która jest wykonywana po zapisaniu modelu.

Mam ten model, Claim, który ma atrybut „status”, który zmienia się w zależności od stanu roszczenia, możliwe wartości oczekują, zatwierdzone, zatwierdzone, odrzucone

Baza danych ma „stan” z domyślną wartością „oczekująca”.

Chciałbym wykonać określone zadania po utworzeniu modelu po raz pierwszy lub zaktualizowaniu go z jednego stanu do drugiego, w zależności od tego, z jakiego stanu się zmienia.

Mój pomysł to mieć funkcję w modelu:

    after_save :check_state

    def check_state
      # if status changed from nil to pending (created)
      do this

      # if status changed from pending to approved
      performthistask
     end

Moje pytanie brzmi: jak sprawdzić poprzednią wartość przed zmianą w modelu?

David C.
źródło

Odpowiedzi:

173

Powinieneś spojrzeć na moduł ActiveModel :: Dirty : powinieneś być w stanie wykonać następujące akcje na swoim modelu Claim:

claim.status_changed?  # returns true if 'status' attribute has changed
claim.status_was       # returns the previous value of 'status' attribute
claim.status_change    # => ['old value', 'new value'] returns the old and 
                       # new value for 'status' attribute

claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}

O! radości z Rails!

Harish Shetty
źródło
6
To nie zadziała po zapisaniu modelu, o co prosił.
Tom Rossi
4
@TomRossi, dirtywywołania działają w after_save(zarówno w Railsach 2.3, jak i 3.x). Użyłem go kilka razy.
Harish Shetty
11
@TomRossi, brudne flagi są resetowane po zatwierdzeniu, więc nie będą dostępne w after_commitwywołaniach zwrotnych wprowadzonych w Railsach 3.x. Na pewno się sprawdzą after_save.
Harish Shetty
Nie mam pojęcia! Myślałem, że zostały zresetowane po zapisaniu!
Tom Rossi
5
@TomRossi Zacząłem z tym samym założeniem kilka lat temu. Kiedy próbowałem sprawdzić brudne flagi w after_save, zadziałało. W istocie after_savejest to wywołanie zwrotne dla stanu między after DMLa before_commit. Możesz zakończyć całą transakcję after_save, rzucając wyjątek. Jeśli chcesz coś zrobić po zapisaniu bez wpływu na bieżącą operację, użyj after_commit:-)
Harish Shetty
38

możesz tego użyć

self.changed

zwraca tablicę wszystkich kolumn, które uległy zmianie w tym rekordzie

możesz także użyć

self.changes

która zwraca jako tablice skrót kolumn, które uległy zmianie oraz wyniki przed i po

zeacuss
źródło
7
Tylko drobna uwaga, aby powiedzieć, że nie ma potrzeby używania self.na nich - możesz po prostu powiedzieć changedi changes.
user664833
@ user664833 Dokładniej, możesz pominąć, selfgdy jesteś w samym modelu, ale możesz je wywołać na dowolnym obiekcie za pomocą object.changedi object.changes. :)
Joshua Pinter
4

Polecam przyjrzeć się jednej z dostępnych wtyczek automatu stanów:

Każdy z nich pozwoli ci ustawić stany i przejścia między stanami. Bardzo przydatny i łatwy sposób obsługi Twoich wymagań.

Toby Hede
źródło
Daję rubyist-aasm spróbować. Powiedzmy, że mam klasę Claim <ActiveRecord :: Base include AASM aasm_column: status aasm_initial_state: pending aasm_state: pending,: enter =>: enter_pending def enter_pending Notifier.deliver_pending_notification (self) end end A moje pole statusu w mojej bazie danych ma wartość domyślną z „oczekujących”. Gdybym miał wykonać Claim.create bez wypełniania pola statusu (aby działał „w toku”), czy AASM uruchomi metodę „enter_pending”?
David C
2

W przypadku Railsów 5.1+ powinieneś używać metody atrybutu aktywnego rekordu: save_change_to_attribute?

save_change_to_attribute? (attr_name, ** opcje) `

Czy ten atrybut zmienił się, kiedy ostatnio zapisywaliśmy? Tę metodę można wywołać jako saved_change_to_name?zamiast saved_change_to_attribute?("name"). Zachowuje się podobnie do attribute_changed?. Ta metoda jest przydatna w wywołaniach zwrotnych po wywołaniu, aby określić, czy wywołanie zapisywania zmieniło określony atrybut.

Opcje

from Po przekazaniu ta metoda zwróci wartość false, chyba że oryginalna wartość jest równa danej opcji

to Po przekazaniu ta metoda zwróci false, chyba że wartość została zmieniona na podaną wartość

Więc twój model będzie wyglądał tak, jeśli chcesz wywołać jakąś metodę opartą na zmianie wartości atrybutu:

class Claim < ApplicationRecord
  
  after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }

  after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }

  
  def do_this
    ..
    ..
  end

  def do_that
    ..
    ..
  end

end

A jeśli nie chcesz sprawdzać zmiany wartości w wywołaniu zwrotnym, możesz wykonać następujące czynności:

class Claim < ApplicationRecord

  after_save: :do_this, if: saved_change_to_status?


  def do_this
    ..
    ..
  end

end
Rajkaran Mishra
źródło
0

Widziałem, jak pytanie pojawia się w wielu miejscach, więc napisałem dla niego mały rubygem, aby kod był trochę ładniejszy (i uniknął miliona stwierdzeń if / else wszędzie): https://github.com/ronna-s / on_change . Mam nadzieję że to pomogło.

Ronna
źródło
0

Znacznie lepiej będzie, jeśli użyjesz dobrze przetestowanego rozwiązania, takiego jak gem state_machine .

Paz Aricha
źródło