Używając ActiveRecord, czy istnieje sposób na uzyskanie starych wartości rekordu podczas after_update

81

Konfiguracja za pomocą prostego przykładu: Mam 1 tabelę ( Totals), która przechowuje sumę amountkolumny każdego rekordu w drugiej tabeli ( Things).

Kiedy a thing.amountzostanie zaktualizowany, chciałbym po prostu dodać różnicę między starą wartością a nową wartością do total.sum.

W tej chwili odejmuję w self.amounttrakcie before_updatei dodam w self.amounttrakcie after_update. To zbytnie pokłada zaufanie w powodzeniu aktualizacji.

Ograniczenie: nie chcę po prostu ponownie obliczać sumy wszystkich transakcji.

Pytanie: Po prostu chciałbym uzyskać dostęp do oryginalnej wartości podczas after_updatewywołania zwrotnego. Jakie sposoby wymyśliłeś, aby to zrobić?

Aktualizacja: idę z pomysłem Luke'a Francla. Podczas after_updateoddzwonienia nadal masz dostęp do self.attr_waswartości, które są dokładnie takie, jakie chciałem. Zdecydowałem się również na after_updateimplementację, ponieważ chcę zachować tego rodzaju logikę w modelu. Dzięki temu bez względu na to, jak zdecyduję się aktualizować transakcje w przyszłości, będę wiedział, że poprawnie aktualizuję sumę transakcji. Dziękujemy wszystkim za sugestie dotyczące wdrożenia.

Abel
źródło

Odpowiedzi:

149

Podobnie jak wszyscy mówią o transakcjach.

To mówi...

ActiveRecord od wersji Rails 2.1 śledzi wartości atrybutów obiektu. Więc jeśli masz atrybut total, będziesz mieć total_changed?metodę i total_wasmetodę, która zwraca starą wartość.

Nie musisz już niczego dodawać do swojego modelu, aby to śledzić.

Aktualizacja: Oto dokumentacja dla ActiveModel :: Dirty zgodnie z żądaniem.

Luke Francl
źródło
Myślałem, że coś takiego istnieje. Czy możesz podać link do odpowiedniej dokumentacji dotyczącej attr_changed? i attr_was?
Gabe Hollombe
Jasne, dodałem link do odpowiedzi.
Luke Francl
10

Niektórzy wspominają o zawarciu tego wszystkiego w transakcji, ale myślę, że to zrobiono dla ciebie; wystarczy wyzwolić wycofywanie, zgłaszając wyjątek dla błędów w wywołaniach zwrotnych after_ *.

Zobacz http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Cały łańcuch wywołań zwrotnych zapisywania, zapisywania! Lub niszczenia przebiega w ramach transakcji. Obejmuje to haki after_ *. Jeśli wszystko pójdzie dobrze, po zakończeniu łańcucha wykonywany jest COMMIT.

Jeśli wywołanie zwrotne before_ * anuluje akcję, wykonywany jest ROLLBACK. Możesz również wyzwolić ROLLBACK, podnosząc wyjątek w dowolnym wywołaniu zwrotnym, w tym przechwyceniu after_ *. Należy jednak pamiętać, że w takim przypadku klient musi być tego świadomy, ponieważ zwykłe zapisanie wywoła taki wyjątek zamiast po cichu zwrócić fałsz.

Gabe Hollombe
źródło
10

Dołączenie „_was” do atrybutu da ci poprzednią wartość przed zapisaniem danych.

Metody te nazywane są metodami brudnymi .

Twoje zdrowie!

Manish Shrivastava
źródło
8

Aby uzyskać wszystkie zmienione pola, odpowiednio ze starymi i nowymi wartościami:

person = Person.create!(:name => 'Bill')
person.name = 'Bob'
person.save
person.changes        # => {"name" => ["Bill", "Bob"]}
thomax
źródło
5
Uważam, że ostatnia linia powinna być teraz czytana person.previous_changeszamiastperson.changes
Jason Axelson,
5

ActiveRecord :: Dirty to moduł wbudowany w ActiveRecord do śledzenia zmian atrybutów. Możesz więc użyć, thing.amount_wasaby uzyskać starą wartość.

John Topley
źródło
3

Dodaj to do swojego modelu:

def amount=(new_value)
    @old_amount = read_attribute(:amount)
    write_attribute(:amount,new_value)
end

Następnie użyj @old_amount w swoim kodzie after_update.

Daniel Von Fange
źródło
0

Po pierwsze, powinieneś to zrobić w transakcji, aby upewnić się, że Twoje dane zostaną zapisane razem.

Aby odpowiedzieć na twoje pytanie, możesz po prostu ustawić zmienną składową na starą wartość w before_update, do której możesz uzyskać dostęp w after_update, jednak nie jest to zbyt eleganckie rozwiązanie.

jonnii
źródło
Staram się zobaczyć, jak zrealizowałbym to, co chcę, za pomocą transakcji. Czy taka transakcja istnieje w modelu czy w kontrolerze? Czy mogę usunąć moje wywołania zwrotne „after_update” i „before_update”? Wreszcie, jak uzyskać starą wartość potrzebną do określenia różnicy?
Abel
nieważne, widzę, że muszę umieścić kod w kontrolerze. były dobre.
Abel
0

Pomysł 1: Zapakuj aktualizację w transakcję bazy danych, aby w przypadku niepowodzenia aktualizacji tabela Totals nie uległa zmianie: Dokumentacja transakcji ActiveRecord

Pomysł 2: Zachowaj starą wartość w @old_total podczas przed_aktualizacją.

bradheintz
źródło
0

Jeśli chcesz uzyskać wartość danego pola po aktualizacji możesz skorzystać z metody field_before_last_save.

Example:

u = User.last
u.update(name: "abc")

u.name_before_last_save will give you old value (before update value)
Jigar Bhatt
źródło