Визначте, які атрибути були змінені в режимі зворотного виклику Rails after_save?


153

Встановлюю зворотний виклик after_save у моїй спостерігачі моделі, щоб надсилати сповіщення лише в тому випадку, якщо опублікований атрибут моделі був змінений з хибного на істинне. Оскільки такі методи, як змінилися? корисні лише до того, як модель буде збережена, так, як я зараз (і безуспішно) намагаюсь це зробити:

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

Хто-небудь має пропозиції щодо найкращого способу впоратися з цим, бажано, використовуючи модель зворотних викликів спостерігачів (щоб не забруднити мій код контролера)?

Відповіді:


182

Рейки 5.1+

Використання 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

Або , якщо ви віддаєте перевагу, saved_change_to_attribute?(:published).

Рейки 3–5.1

Увага

Такий підхід працює через Rails 5.1 (але застарілий у 5.1 і має різкі зміни в 5.2). Про зміну цього запиту на потяг можна прочитати .

У вашому after_updateфільтрі на моделі ви можете використовувати _changed?аксесуар. Так, наприклад:

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

Це просто працює.


Забудьте те, що я сказав вище - це НЕ працює в Rails 2.0.5. Тож корисне доповнення до Rails 3.
stephenr

4
Я думаю, що після_актуація застаріла зараз? У всякому разі, я спробував це в гачці after_save, і це, здавалося, працює добре. (Мабуть, зміни () хеш досі не були скинуті в режимі after_save, мабуть.)
Тайлер Рік

3
Мені довелося включити цей рядок у файл model.rb. включити ActiveModel :: брудний
coderVishal

13
У пізніших версіях Rails ви можете додати умову до after_updateвиклику:after_update :send_notification_after_change, if: -> { published_changed? }
Koen.

11
У Rails 5.2 відбудуться деякі зміни API. Вам доведеться зробити saved_change_to_published?або saved_change_to_publishedотримати зміну під час зворотного дзвінка
alopez02

183

Для тих, хто хоче знати зміни, щойно внесені в after_save зворотного дзвінка:

Рейки 5.1 і більше

model.saved_changes

Рейки <5.1

model.previous_changes

Також дивіться: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes


2
Це прекрасно працює, коли ви не хочете використовувати зворотні дзвінки в моделях і потребуєте дійсного збереження, перш ніж виконувати подальші функції.
Дейв Робертсон

Це прекрасно! Дякуємо, що опублікували це.
jrhicks

Дивовижно! Дякую за це
Даніель Логан

4
Просто щоб було зрозуміло: у моїх тестах (Rails 4), якщо ви використовуєте after_saveзворотний дзвінок, self.changed?є trueі self.attribute_name_changed?є true, але self.previous_changesповертає порожній хеш.
sandre89

9
Це застаріло в Rails 5.1 +. Використання saved_changesв after_saveзворотних виклики замість
rico_mac

71

Кожен, хто бачить це пізніше, як це наразі (серпня 2017 року) очолює google: Варто зазначити, що ця поведінка буде змінена в Rails 5.2 та має попередження про депресію щодо Rails 5.1, оскільки ActiveModel :: Dirty трохи змінився .

Що я можу змінити?

Якщо ви використовуєте attribute_changed?метод у after_*-викликах, ви побачите попередження типу:

ПОПЕРЕДЖЕННЯ ПОПЕРЕДЖЕННЯ. У attribute_changed?наступній версії Rails поведінка всередині зворотних зворотних дзвінків буде змінюватися. Нове значення повернення відображатиме поведінку виклику методу після saveповернення (наприклад, протилежне тому, що він повертає зараз). Щоб зберегти поточну поведінку, використовуйте saved_change_to_attribute?замість цього. (зателефонував з some_callback на /PATH_TO/app/models/user.rb:15)

Як згадується, ви могли це легко виправити, замінивши функцію на saved_change_to_attribute?. Так, наприклад, name_changed?стає saved_change_to_name?.

Так само, якщо ви використовуєте значення attribute_changeдля отримання попередніх значень, це змінюється і призводить до наступного:

ПОПЕРЕДЖЕННЯ ПОПЕРЕДЖЕННЯ. У attribute_changeнаступній версії Rails поведінка всередині зворотних зворотних дзвінків буде змінюватися. Нове значення повернення відображатиме поведінку виклику методу після saveповернення (наприклад, протилежне тому, що він повертає зараз). Щоб зберегти поточну поведінку, використовуйте saved_change_to_attributeзамість цього. (зателефонували з some_callback на /PATH_TO/app/models/user.rb:20)

Знову ж, як згадується, метод змінює ім'я, до saved_change_to_attributeякого повертається ["old", "new"]. або використовувати saved_changes, що повертає всі зміни, і до них можна отримати доступ як saved_changes['attribute'].


2
Зауважте, що ця корисна відповідь також включає вирішення attribute_wasметодів депресії : saved_change_to_attributeзамість цього скористайтеся .
Джо Атцбергер

47

Якщо ви можете зробити це before_saveзамість цього after_save, ви зможете використовувати це:

self.changed

він повертає масив усіх змінених стовпців цього запису.

Ви також можете використовувати:

self.changes

який повертає хеш стовпців, які були змінені, і до, і після результатів як масиви


8
За винятком того, що вони не працюють у after_зворотному дзвінку, про що йшлося фактично. Відповідь @ jacek-głodek нижче - правильна.
Джаз

Оновлено відповідь, щоб було зрозуміло, що це стосується лишеbefore_save
mahemoff

1
Яка це версія Rails? У Rails 4 self.changedможна використовувати в after_saveзворотному режимі.
sandre89

Чудово працює! Також слід зазначити, що результат self.changed- це масив рядків! (Не символи!)["attr_name", "other_attr_name"]
LukeS

8

"Вибрана" відповідь не працювала для мене. Я використовую рейки 3.1 з CouchRest :: Модель (заснована на Active Model). Ці _changed?методи не повертають вірно для змінених атрибутів в after_updateгачку, тільки вbefore_update гаку. Мені вдалося змусити його працювати, використовуючи (новий?) around_updateГачок:

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

1
Вибрана відповідь для мене також не спрацювала, але це вдалося. Дякую! Я використовую ActiveRecord 3.2.16.
Бен Лі

5

ви можете додати умову до after_updateподібного:

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

  ...
end

немає необхідності додавати умову в самому send_notificationметоді.


-17

Ви просто додаєте аксесуар, який визначає, що ви змінюєте

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

Це дуже погана практика, навіть не враховуйте.
Нуно Сільва
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.