Как полагаться на грязные модели, когда измененные атрибуты чисты в «после обратных вызовов»? - PullRequest
0 голосов
/ 17 января 2019

Я пытаюсь использовать ActiveModel :: Dirty / ActiveRecord :: AttributeMethods :: Dirty в представлениях (например, в update.js.erb) следующим образом:

<% 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

В моей модели: 1011 *

class Product < ApplicationRecord
  after_update :something_that_clears_changed_attributes

  private

  def something_that_clears_changed_attributes
    ...
    self.reload
  end
end

В приведенном выше примере alert("Name changed!") никогда не будет запущен.

На самом деле, кажется, что если во время «потока обновлений» (в контроллере) возникают «обратные вызовы» (на уровне модели), то перезагружает объект или его дальнейшее обновление или сохранение, тогда Вы не можете больше полагаться на Грязный. То есть грязные методы могут возвращать «неожиданные» значения, поскольку объектом «манипулируют» во время потока.

Это относится к моделям, которые используют гемы, которые повторно загружают, обновляют или сохраняют объект несколько раз в обратных вызовах и, таким образом, «делают недействительными» модель Dirty во время потока (даже метод attribute_before_last_save в Rails 5 возвращались бы «неожиданные» значения).

Чтобы решить эту проблему, вы можете использовать первый пример в этом посте (сохраняя - до обновления - изменение атрибутов в переменной для последующего использования), но, возможно, есть лучший способ использовать грязные модели.

Есть идеи о том, как положиться на грязные модели, когда во время потока происходит многократная перезагрузка, обновление, сохранение или другие операции, которые очищают измененные атрибуты?

1 Ответ

0 голосов
/ 18 января 2019

Если вы хотите сослаться на предыдущие изменения в объектах 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
...