Определите, какие атрибуты были изменены в обратном вызове Rails after_save?
Я настраиваю обратный вызов 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
У кого-нибудь есть какие-либо предложения относительно лучшего способа справиться с этим, предпочтительно с помощью обратных вызовов наблюдателя модели (чтобы не загрязнять код моего контроллера)?
8 ответов
В вашем after_update
фильтр по модели, которую вы можете использовать _changed?
аксессор (по крайней мере, в Rails 3, не уверен в Rails 2). Так, например:
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
Это просто работает.
Для тех, кто хочет знать изменения после сохранения, вы должны использовать
model.previous_changes
это работает как model.change
но это все еще работает после model.save
и т.д. Я нашел эту информацию полезной, так что, возможно, вы тоже.
В Rails 5.1+ это устарело. использование saved_changes
в after_save
обратные вызовы вместо.
Любой, кто увидит это позже, так как в настоящее время (август 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']
,
В случае, если вы можете сделать это на before_save
вместо after_save
, вы сможете использовать это:
self.changed
он возвращает массив всех измененных столбцов в этой записи.
Вы также можете использовать:
self.changes
который возвращает хэш столбцов, которые изменились и до и после результатов в виде массивов
"Выбранный" ответ не работал для меня. Я использую рельсы 3.1 с CouchRest::Model (на основе Active Model). _changed?
методы не возвращают true для измененных атрибутов в 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
Вы можете добавить условие к after_update
вот так:
class SomeModel < ActiveRecord::Base
after_update :send_notification, if: :published_changed?
...
end
нет необходимости добавлять условие в send_notification
сам метод.
Я использую это для извлечения хэша с новыми значениями атрибута, что полезно для обновления других моделей
attributes_changed = self.changes.inject(Hash.new){|hash,attr| ((hash[attr[0].to_sym] = attr[1].last) || attr[1].last == false) && hash}
attr[1].last == false
необходимо, когда новое значение false
где присваивание возвращает false, а "hash" не возвращается.
Я думаю, что есть более простой способ, я новичок в рельсах
Вы просто добавляете аксессор, который определяет, что вы меняете
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