当更改后的属性在“回调后”内是干净的时,如何依赖脏模型?

时间:2019-01-17 17:33:32

标签: ruby-on-rails activerecord callback ruby-on-rails-5 activemodel

我试图通过以下方式在视图(例如,在update.js.erb中)中使用ActiveModel::Dirty / ActiveRecord::AttributeMethods::Dirty

<% if @product.saved_change_to_attribute?(:name) %>
  alert("Name changed!")
<% end %>

在我的控制器中,我有:

class ProductsController < ApplicationController
  def update
    @product = Product.find(params[:id])

    respond_to do |format|
      if @product.update(product_params)
        format.js { render(:action => :show) }
      else
        format.js { render(:action => :update) }
      end
    end
  end

  private

  def product_params
    params.require(:product).permit(:price)
  end
end

在我的模型中,我有:

class Product < ApplicationRecord
  after_update :something_that_clears_changed_attributes

  private

  def something_that_clears_changed_attributes
    ...
    self.reload
  end
end

在上面的示例中,alert("Name changed!")永远不会被触发。

实际上,似乎如果在“更新流”(在控制器内)期间存在(在模型级别)reload对象的“ after回调”或进一步更新或保存它,则您将无法不再依赖肮脏。也就是说,Dirty方法可以返回“意外”值,因为对象在流期间被“操纵”。

使用使用gem的模型会在回调中多次重载,更新或保存对象,因此在流程期间使Dirty模型“无效”(即使在Rails 5中使用attribute_before_last_save方法也是如此)返回“意外”值)。

要解决此问题,您可以使用this post中的第一个示例(通过在更新前将变量中的更改属性保留下来以供以后使用),但是也许有更好的方法来使用Dirty模型

在流程中有多个重新加载,更新,保存或其他清除更改的属性的方法时,是否有关于如何依赖Dirty模型的想法?

1 个答案:

答案 0 :(得分:0)

如果您要引用对ActiveRecord对象的先前更改(在更改持续存在并重新加载对象之后),我建议创建一个单独的表(例如product_dirties)以跟踪所述更改。让我们以您的Product模型为例:

首先,您将要创建一个product_dirties

create_table :product_dirties do |t|
  t.references :product,      index: true, foreign_key: true
  t.jsonb      :modifications
end

然后,添加ProductDirty模型

class ProductDirty < ApplicationRecord
  belongs_to :product, inverse_of: :product_dirties

  validates_presence_of :product, :modifications
end

并更新您的Product模型,使其包含新的关联以及在进行更改时创建脏记录的回调:

class Product < ApplicationRecord
  has_many :product_dirties, inverse_of: :product

  before_save :create_dirty_record, if: -> { changed? }

  private

  def create_dirty_record
    # modifications will be saved in this format: {"name"=>"new name here!"}
    attribute_changes = ActiveSupport::HashWithIndifferentAccess[self.changed.map{ |attr| [attr, self.method(attr).call] }]

    if attribute_changes.present?
      ProductDirty.find_or_create_by(product: self).update_attribute(:modifications, attribute_changes)
    end

    self.restore_attributes # <- add this if you'd like to revert the changes to the product and keep them separate in the `product_dirties` table
  end
end

然后可以将一种方法添加到Product模型中,以查找更改。由于我们实际上并未保留更改(请参见上面apply_dirty_record旁的注释),因此我们在父模型(例如Product)中添加了self.restore_attributes方法(见下文)。

def apply_dirty_record
  dirty_record = ProductDirty.find_by(product: self)
  self.assign_attributes(dirty_record.modifications) if dirty_record
end