Determine what attributes were changed in Rails af

2019-01-16 02:54发布

I'm setting up an after_save callback in my model observer to send a notification only if the model's published attribute was changed from false to true. Since methods such as changed? are only useful before the model is saved, the way I'm currently (and unsuccessfully) trying to do so is as follows:

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

Does anyone have any suggestions as to the best way to handle this, preferably using model observer callbacks (so as not to pollute my controller code)?

8条回答
狗以群分
2楼-- · 2019-01-16 03:08

To anyone seeing this later on, as it currently (Aug. 2017) tops google: It is worth mentioning, that this behavior will be altered in Rails 5.2, and has deprecation warnings as of Rails 5.1, as ActiveModel::Dirty changed a bit.

What do I change?

If you're using attribute_changed? method in the after_*-callbacks, you'll see a warning like:

DEPRECATION WARNING: The behavior of attribute_changed? inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after save returned (e.g. the opposite of what it returns now). To maintain the current behavior, use saved_change_to_attribute? instead. (called from some_callback at /PATH_TO/app/models/user.rb:15)

As it mentions, you could fix this easily by replacing the function with saved_change_to_attribute?. So for example, name_changed? becomes saved_change_to_name?.

Likewise, if you're using the attribute_change to get the before-after values, this changes as well and throws the following:

DEPRECATION WARNING: The behavior of attribute_change inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after save returned (e.g. the opposite of what it returns now). To maintain the current behavior, use saved_change_to_attribute instead. (called from some_callback at /PATH_TO/app/models/user.rb:20)

Again, as it mentions, the method changes name to saved_change_to_attribute which returns ["old", "new"]. or use saved_changes, which returns all the changes, and these can be accessed as saved_changes['attribute'].

查看更多
一夜七次
3楼-- · 2019-01-16 03:09

For those who want to know the changes after save, you should use

model.previous_changes

this works like model.change but it works still after model.save, etc. I found this information useful so maybe you will too.

In Rails 5.1+ this is deprecated. Use saved_changes in after_save callbacks instead.

查看更多
爱情/是我丢掉的垃圾
4楼-- · 2019-01-16 03:09

In case you can do this on before_save instead of after_save, you'll be able to use this:

self.changed

it returns an array of all changed columns in this record.

you can also use:

self.changes

which returns a hash of columns that changed and before and after results as arrays

查看更多
放我归山
5楼-- · 2019-01-16 03:26

you can add a condition to the after_update like so:

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

  ...
end

there's no need to add a condition within the send_notification method itself.

查看更多
再贱就再见
6楼-- · 2019-01-16 03:28

The "selected" answer didn't work for me. I'm using rails 3.1 with CouchRest::Model (based on Active Model). The _changed? methods don't return true for changed attributes in the after_update hook, only in the before_update hook. I was able to get it to work using the (new?) around_update hook:

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
查看更多
你好瞎i
7楼-- · 2019-01-16 03:28

You just add an accessor who define what you change

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
查看更多
登录 后发表回答