如何获取关联记录的先前值?

时间:2017-10-25 12:45:30

标签: ruby-on-rails ruby callback associated-object

在处理多对多关系时,我需要维护记录更改值的日志文件。 使用before_save和after_save回调适用于main(has_many)模型本身,但在before_save回调中,关联的(belongs_to)记录似乎已经更新! 看到数据的某些部分在“之前”之前已经更新似乎很奇怪。调用回调。

此外,在关联模型中使用回调可以发现没有执行before_destroy。只调用before_save并显示新值。 我也试过了:prepend => :真正的选择,但没有给出其他结果。

在main(has_many)模型中实际保存之前打开SQL日志记录时,我可以看到Rails正在获取相关记录,确定差异并删除剩余记录。不调用关联模型的before_destroy。 然后它调用相关模型的before_save并插入新的模型(如果有的话)并提交事务。这只是在主模型的before_save之前完成的。

有人知道如何在更改之前获取相关记录吗? 我希望相关模型的before_destroy可以被调用,让我来处理它。

3 个答案:

答案 0 :(得分:0)

使用before_update,您可以使用 _was

访问旧值
before_update :record_values


def record_values
  p "oldvalue:  #{self.field_was} Newvalue: #{self.field}"
end

答案 1 :(得分:0)

您的问题有点不清楚,但让我向您提供一个我认为您尝试做的例子。

以下代码适用于我:

class Person < ApplicationRecord
  has_many :addresses

  validates_presence_of :name

  before_save { puts "before_save of person - changes: #{changes}" }
  before_destroy { puts "before_destroy of person with id: #{id}" }
end

class Address < ApplicationRecord
  belongs_to :person, required: true

  validates_presence_of :name

  before_save { puts "before_save of address - changes: #{changes}" }
  before_destroy { puts "before_destroy of address with id: #{id}" }
end

交互时会产生以下输出:

person = Person.create(name: 'Johan Wentholt')
# before_save of person - changes: {"name" =>[nil, "Johan Wentholt"]}
#=> #<Person id: 2, name: "Johan Wentholt", created_at: "2017-10-25 15:04:27", updated_at: "2017-10-25 15:04:27">

person.addresses.create(name: 'Address #1')
# before_save of address - changes: {"person_id"=>[nil, 2], "name"  =>[nil, "Address #1"]}
#=> #<Address id: 7, person_id: 2, name: "Address #1", created_at: "2017-10-25 15:06:38", updated_at: "2017-10-25 15:06:38">

person.addresses.last.update(name: 'Address without typo')
# before_save of address - changes: {"name"=>["Address #1", "Address without typo"]}
#=> true

person.update(name: 'Kaasboer')
# before_save of person - changes: {"name"=>["Johan Wentholt", "Kaasboer"]}
#=> true

person.addresses.last.destroy
# before_destroy of address with id: 7
#=> #<Address id: 7, person_id: 2, name: "Address without typo", created_at: "2017-10-25 15:06:38", updated_at: "2017-10-25 15:08:51">

person.destroy
# before_destroy of person with id: 2
#=> #<Person id: 2, name: "Kaasboer", created_at: "2017-10-25 15:04:27", updated_at: "2017-10-25 15:10:46">

如您所见,这会记录所有更改。就像我说的那样,问题有点不清楚,但我希望这会对你有所帮助。

请记住,某些Rails方法不会触发回调。例如:删除 update_all update_column 和其他一些。

有关更改的更多信息,请查看:ActiveModel::Dirty

答案 2 :(得分:0)

为清楚起见,我们提供一些扩展信息:

class Book < ActiveRecord::Base
  unloadable
  has_many :titles, dependent: :destroy
  has_many :authors, :through => :titles
  accepts_nested_attributes_for :authors

  before_save :pre_save
  after_save  :post_save
  before_destroy :pre_delete

  def pre_save
    @nr = self.new_record?
  end

  def pre_save
    changed_values = []
    if @nr
      changed_values.push "New record created"
    else 
      self.changes.each do |field, cvs|
        changes.push("#{field} : #{cvs[0]} => #{cvs[1]}")
      end
    end
    if changes.length > 0
      BookLog.create(:book_id => self.id, :changed_values => changes.join(', '))
    end
  end

  def pre_delete
    BookLog.create(:book_id => self.id, :changed_values => "Deleted: #{self.name}")
  end
end

class Title < ActiveRecord::Base
  unloadable
  belongs_to :book
  belongs_to :author
end

class Author < ActiveRecord::Base
  unloadable
  has_many :titles, dependent: :destroy
  has_many :books, :through => :titles
  accepts_nested_attributes_for :books
end

class BooksController < ApplicationController

  def edit
    book = Book.find(params[:book][:id])
    book.name = .....
    ===> Here the old values are still available <==== 
    book.author_ids = params[:book][:author_ids] 
    ===> Now the new values are written to the database! <==== 
    book.save!
  end
end

完整记录对Book记录的更改。 但是无法获取author_ids的已更改关联值。 未调用Title中的before_destroy回调,after_save为。

我在将新的author_ids分配给已编辑的记录之前启用了SQL日志记录。我可以看到Rails确定现有和新关联值之间的差异,从Titles表中删除剩余并插入额外的值(如果有的话)

我通过将旧标题与新值进行比较,将标题中的更改日志记录移动到Books控制器来解决它:

     o_authors = book.author_ids
     n_authors = params[:book][:author_ids].collect {|c| c.to_i}
     diff = o_authors - n_authors | n_authors - o_authors
     if !diff.empty?
       changed_values = []
       (o_authors - n_authors).each do |d|
         changed_values.push("Removed Author: #{Author.find(d).name}")
       end
       (n_authors - o_authors).each do |d|
         changed_values.push("Added Author: #{Author.find(d).name}")
       end
       BookLog.create(:book_id => book.id, :changed_values => changed_values)
     end
     book.author_ids = params[:book][:author_ids]
     book.save!

就像我说的,它有效,但恕我直言,它并没有反映出Rails的做事方式。我希望以与任何其他Book属性相同的方式获取之前的author_ids。