Określić, jakie atrybuty zostały zmienione w Railsach after_save callback?

153

Ustawiam wywołanie zwrotne after_save w obserwatorze modelu, aby wysłać powiadomienie tylko wtedy, gdy atrybut opublikowania modelu został zmieniony z false na true. Od kiedy zmieniły się takie metody ? są przydatne tylko przed zapisaniem modelu, sposób, w jaki obecnie (i bezskutecznie) próbuję to zrobić, jest następujący:

def before_save(blog)
  @og_published = blog.published?
end

def after_save(blog)
  if @og_published == false and blog.published? == true
    Notification.send(...)
  end
end

Czy ktoś ma jakieś sugestie, jak najlepiej sobie z tym poradzić, najlepiej używając wywołań zwrotnych obserwatora modelu (aby nie zanieczyszczać mojego kodu kontrolera)?

modulaaron
źródło

Odpowiedzi:

182

Rails 5.1+

Zastosowanie saved_change_to_published?:

class SomeModel < ActiveRecord::Base
  after_update :send_notification_after_change

  def send_notification_after_change
    Notification.send(…) if (saved_change_to_published? && self.published == true)
  end

end

Lub jeśli wolisz, saved_change_to_attribute?(:published).

Szyny 3–5.1

Ostrzeżenie

To podejście działa w Railsach 5.1 (ale jest przestarzałe w 5.1 i ma istotne zmiany w 5.2). Możesz przeczytać o zmianie w tym żądaniu ściągnięcia .

W swojej after_updatefiltra od modelu mogą korzystać _changed?accessor. Na przykład:

class SomeModel < ActiveRecord::Base
  after_update :send_notification_after_change

  def send_notification_after_change
    Notification.send(...) if (self.published_changed? && self.published == true)
  end

end

Po prostu działa.

Radek Paviensky
źródło
Zapomnij o tym, co powiedziałem powyżej - NIE DZIAŁA w Railsach 2.0.5. Tak więc przydatny dodatek do Rails 3.
stephenr
4
Myślę, że after_update jest teraz przestarzałe? W każdym razie próbowałem tego w haku after_save i wydawało się, że działa dobrze. (Widocznie hash zmian () nadal nie został jeszcze zresetowany w after_save.)
Tyler Rick,
3
Musiałem umieścić tę linię w pliku model.rb. to ActiveModel :: Dirty
coderVishal
13
W późniejszych wersjach Railsów możesz dodać warunek do after_updatewywołania:after_update :send_notification_after_change, if: -> { published_changed? }
Koen.
11
W Railsach 5.2 pojawią się pewne zmiany w API. Będziesz musiał zrobić saved_change_to_published?lub saved_change_to_publishedpobrać zmianę podczas oddzwaniania
alopez02
183

Dla tych, którzy chcą poznać zmiany wprowadzone właśnie w after_saveoddzwonieniu:

Rails 5.1 i nowsze

model.saved_changes

Szyny <5.1

model.previous_changes

Zobacz także: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes

Jacek Głodek
źródło
2
Działa to doskonale, gdy nie chcesz używać wywołań zwrotnych modelu i potrzebujesz prawidłowego zapisu przed wykonaniem dalszych funkcji.
Dave Robertson,
To jest doskonałe! Dziękuję za wysłanie tego.
jrhicks
Niesamowite! Dzięki za to.
Daniel Logan
4
Dla jasności: w moich testach (Rails 4), jeśli używasz after_savewywołania zwrotnego, self.changed?jest truei self.attribute_name_changed?jest również true, ale self.previous_changeszwraca pusty hash.
sandre89
9
Jest to przestarzałe w Railsach 5.1 +. Zamiast tego użyj saved_changesw after_savecallbackach
rico_mac
71

Dla każdego, kto zobaczy to później, ponieważ obecnie (sierpień 2017) jest na szczycie Google: Warto wspomnieć, że to zachowanie zostanie zmienione w Railsach 5.2 i ma ostrzeżenia o wycofaniu z Railsów 5.1, jako ActiveModel :: Dirty zmienił się nieco .

Co mam zmienić?

Jeśli używasz attribute_changed?metody w after_*-callbacks, zobaczysz ostrzeżenie takie jak:

OSTRZEŻENIE O DEPRECATION: Zachowanie attribute_changed?inside lub after callbacka ulegnie zmianie w następnej wersji Railsów. Nowa wartość zwracana będzie odzwierciedlać zachowanie wywołania metody po savezwróceniu (np. Przeciwieństwo tego, co zwraca teraz). Aby zachować bieżące zachowanie, użyj saved_change_to_attribute?zamiast tego. (wywoływane z some_callback pod adresem /PATH_TO/app/models/user.rb:15)

Jak wspomniano, można to łatwo naprawić, zastępując funkcję saved_change_to_attribute?. Na przykład name_changed?staje się saved_change_to_name?.

Podobnie, jeśli używasz attribute_changedo uzyskania wartości przed-po, to również się zmieni i wyrzuci następujące:

OSTRZEŻENIE O DEPRECATION: Zachowanie attribute_changeinside lub after callbacka ulegnie zmianie w następnej wersji Railsów. Nowa wartość zwracana będzie odzwierciedlać zachowanie wywołania metody po savezwróceniu (np. Przeciwieństwo tego, co zwraca teraz). Aby zachować bieżące zachowanie, użyj saved_change_to_attributezamiast tego. (wywoływane z some_callback pod adresem /PATH_TO/app/models/user.rb:20)

Ponownie, jak wspomniano, metoda zmienia nazwę, do saved_change_to_attributektórej zwraca ["old", "new"]. lub use saved_changes, co zwraca wszystkie zmiany, a te są dostępne jako saved_changes['attribute'].

Frederik Spang
źródło
2
Zwróć uwagę, że ta pomocna odpowiedź zawiera również obejścia dla wycofania attribute_wasmetod: użyj saved_change_to_attributezamiast tego.
Joe Atzberger
47

Jeśli możesz to zrobić before_savezamiast after_save, będziesz mógł użyć tego:

self.changed

zwraca tablicę wszystkich zmienionych kolumn w tym rekordzie.

możesz także użyć:

self.changes

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

zeacuss
źródło
8
Tyle że te nie działają w after_wywołaniu zwrotnym, o co właściwie chodziło w tym pytaniu. Odpowiedź @ jacek-głodek poniżej jest prawidłowa.
Jazz
Zaktualizowałem odpowiedź, aby wyjaśnić, że dotyczy to tylkobefore_save
mahemoff
1
Która wersja Railsów to jest? W Railsach 4 self.changedmoże być używany w after_savecallbackach.
sandre89
Działa świetnie! Należy również zauważyć, że wynikiem self.changedjest tablica ciągów! (Nie symbole!)["attr_name", "other_attr_name"]
LukeS
8

„Wybrana” odpowiedź nie działa dla mnie. Używam rails 3.1 z CouchRest :: Model (na podstawie Active Model). Te _changed?metody nie zwróci true dla zmienionych atrybutów w after_updatehaku, tylko w before_updatehaku. Udało mi się go uruchomić za pomocą (nowego?) around_updateHaka:

class SomeModel < ActiveRecord::Base
  around_update :send_notification_after_change

  def send_notification_after_change
    should_send_it = self.published_changed? && self.published == true

    yield

    Notification.send(...) if should_send_it
  end

end
Jeff Gran
źródło
1
Wybrana odpowiedź również nie działała dla mnie, ale tak. Dzięki! Używam ActiveRecord 3.2.16.
Ben Lee,
5

możesz dodać warunek do czegoś after_updatepodobnego, tak:

class SomeModel < ActiveRecord::Base
  after_update :send_notification, if: :published_changed?

  ...
end

nie ma potrzeby dodawania warunku w send_notificationsamej metodzie.

Echo
źródło
-17

Wystarczy dodać akcesor, który określa, co zmieniasz

class Post < AR::Base
  attr_reader :what_changed

  before_filter :what_changed?

  def what_changed?
    @what_changed = changes || []
  end

  after_filter :action_on_changes

  def action_on_changes
    @what_changed.each do |change|
      p change
    end
  end
end
shingara
źródło
To bardzo zła praktyka, nawet o tym nie myśl.
Nuno Silva