Определите, какие атрибуты были изменены в обратном вызове Rails after_save?

153

Я настраиваю обратный вызов after_save в моем обозревателе модели для отправки уведомления, только если атрибут публикации модели был изменен с false на true. Так как методы, такие как изменились? полезны только перед сохранением модели, так как я сейчас (и безуспешно) пытаюсь сделать это следующим образом:

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

У кого-нибудь есть какие-либо предложения относительно лучшего способа справиться с этим, предпочтительно с помощью обратных вызовов наблюдателя модели (чтобы не загрязнять код моего контроллера)?

modulaaron
источник

Ответы:

182

Rails 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_update устарела сейчас? Во всяком случае, я попробовал это в ловушке after_save, и это, казалось, работало нормально. (Хэш изменения () все еще не был сброшен в after_save, по-видимому.)
Тайлер Рик
3
Мне пришлось включить эту строку в файл model.rb. включить ActiveModel :: Dirty
coderVishal
13
В более поздних версиях Rails вы можете добавить условие к after_updateвызову:after_update :send_notification_after_change, if: -> { published_changed? }
Koen.
11
Будут некоторые изменения API в Rails 5.2. Вам нужно будет сделать saved_change_to_published?или saved_change_to_publishedполучить изменения во время обратного вызова
alopez02
183

Для тех, кто хочет знать изменения, сделанные только что в after_saveобратном вызове:

Rails 5.1 и выше

model.saved_changes

Рельсы <5.1

model.previous_changes

Также см .: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html#method-i-previous_changes

Яцек Глодек
источник
2
Это прекрасно работает, когда вы не хотите использовать обратные вызовы модели и нуждаетесь в действительном сохранении перед выполнением дальнейших функций.
Дэйв Робертсон
Это потрясающе! Спасибо за публикацию.
придурки
Потрясающие! Спасибо за это.
Даниэль Логан
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обратных
вызовах
71

Любой, кто увидит это позже, так как он в настоящее время (август 2017 г.) возглавляет Google: стоит упомянуть, что это поведение будет изменено в Rails 5.2 и имеет предупреждения об устаревании, начиная с Rails 5.1, как ActiveModel :: Dirty немного изменилась ,

Что я могу изменить?

Если вы используете attribute_changed?метод в after_*-callbacks, вы увидите предупреждение вроде:

ПРЕДУПРЕЖДЕНИЕ ОБ УСТАРЕВАНИИ: Поведение 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

который возвращает хэш столбцов, которые изменились и до и после результатов в виде массивов

zeacuss
источник
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 :: Model (на основе 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
shingara
источник
Это очень плохая практика, даже не думайте об этом.
Нуно Силва