更改 ActiveRecord 属性的回调?

19

我知道ActiveRecord::Dirty和相关的方法,但我没看到有一种方式可以订阅属性更改事件。类似这样的:

class Person < ActiveRecord::Base
  def attribute_changed(attribute_name, old_value, new_value)
  end

  #or

  attribute_changed do |attribute_name, old_value, new_value|
  end
end

是否有Rails标准或插件可以实现此功能?我感觉一定有,只是我没有找到。

6个回答

18

cwninja的答案应该能解决问题,但还有一点要注意。

首先,基础属性处理是通过write_attribute方法完成的,所以你应该使用它。

Rails也有内置的回调结构可以利用,虽然它不能传递参数,这有点麻烦。

使用自定义回调,你可以这样做:

class Person < ActiveRecord::Base

  def write_attribute(attr_name, value)
    attribute_changed(attr_name, read_attribute(attr_name), value)
    super
  end

  private

    def attribute_changed(attr, old_val, new_val)
      logger.info "Attribute Changed: #{attr} from #{old_val} to #{new_val}"
    end

 end

如果你想尝试使用Rails的回调函数(特别是在有多个回调和/或子类化的情况下很有用),可以像这样做:

class Person < ActiveRecord::Base
  define_callbacks :attribute_changed

  attribute_changed :notify_of_attribute_change

  def write_attribute(attr_name, value)
    returning(super) do
      @last_changed_attr = attr_name
      run_callbacks(:attribute_changed)
    end
  end

  private

    def notify_of_attribute_change
      attr = @last_changed_attr
      old_val, new_val = send("#{attr}_change")
      logger.info "Attribute Changed: #{attr} from #{old_val} to #{new_val}"
    end

end

Peter,这太好了,将节约我在我的代码中到处写的 "write_attribute"。谢谢! - BushyMark
似乎这只适用于Rails 2。 - lulalala
通过谷歌搜索进入此页面,未注意日期。这篇较新的指南满足了我的需求:如何在 after_callbacks 中跟踪模型的更改 - Jay

14

针对Rails 3:

class MyModel < ActiveRecord::Base
  after_update :my_listener, :if => :my_attribute_changed?

  def my_listener
    puts "Attribute 'my_attribute' has changed"
  end
end

注释在:https://dev59.com/4kfRa4cB1Zd3GeqP6BAC#1709956

我没有找到官方文档关于这个的。


2
我认为他想要一个方法在内存中的属性被更改后立即调用,而在保存之前调用,即my_object.some_attr = "foo"将生成回调。 - MikeJ
自Rails 2.3起,after_update已被弃用(http://apidock.com/rails/ActiveRecord/Callbacks/after_update)。 - schmijos

8

对于Rails 4:

def attribute=(value)
  super(value)
  your_callback(self.attribute)
end

如果你想覆盖属性的值,你应该使用write_attribute(:attribute, your_callback(self.attribute))而不是attribute=,否则你会遇到栈溢出异常。


5

尝试:

def attribute_changed(attribute_name, old_value, new_value)
end

def attribute=(attribute_name, value)
  returning(super) do
    attribute_changed(attribute_name, attribute_was(attribute_name), attribute(attribute_name))
  end
end

我刚刚发明了这个方法,但应该可以使用。

3

您可以始终访问私有方法changed_attributes并使用before_save检查其中的键,然后根据需要进行处理。


3
我发现最简单的方法是:

after_save :my_method, :if => Proc.new{ self.my_attribute_changed? }

def my_method
  ... do stuff...
end

1
after_save :my_method, :if => :my_attribute_changed? 在保存后执行:my_method,如果my_attribute已更改? - Wilson Silva
太好了!这甚至更好 :) - pesta

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接