Определите, какие атрибуты были изменены в обратном вызове 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
Другие вопросы по тегам