Save_changes пуст после сохранения изменений в модели Rails

У нас есть приложение Rails 6.1, выполняющее обратный вызов after_commit для проверки значения saved_changes для запуска некоторых фоновых рабочих процессов. Но проблема в том, что saved_changes возвращает пустое значение, если модель имеет accepts_nested_attributes_for. Это не только after_commit другие обратные вызовы, такие как before_save, но и around_save возвращающее пустое saved_changes значение.

Например.

на модели/profile.rb

after_commit :run_worker, on: :update

belongs_to :user
accepts_nested_attributes_for :user

В данном случае saved_changes — это {}, но если я удалю accepts_nested_attributes_for, он будет работать нормально и даст мне измененное значение.

Это известная проблема, вызванная двойным сохранением, как упоминалось в этой проблеме с GH. Обходной путь для версии 7.1 — добавление конфигурации в application.rb

config.active_record.run_commit_callbacks_on_first_saved_instances_in_transaction = true

Это значение по умолчанию предназначено для Rails 7.1 и не работает для версии 6.1. Кто-нибудь еще сталкивался с этой проблемой и имеет обходной путь?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Не только after_commit другие обратные вызовы, такие как before_save, но и around_save также возвращают пустое значение save_changes.

saved_change присутствует только после сохранения записи, поэтому ожидается, что она пуста в обратном вызове before_save, и она должна быть пустой и в обратном вызове around_save, пока запись не будет сохранена там.

Перед сохранением записи грязное состояние сохраняется в объекте changes (а не saved_changes).


Чтобы решить вашу проблему, я бы предложил сохранить changes в пользовательском атрибуте, чтобы использовать его позже в обратных вызовах after_save/after_commit. Что-то вроде этого:

attr_accessor :my_saved_changes

before_save :save_dirty_state

def save_dirty_state
  self.my_saved_changes = changes
end

ОБНОВЛЕННОЕ ПРЕДЛОЖЕНИЕ С учетом комментария ниже:

Если я правильно понимаю, эти 2 сохранения происходят внутри одной транзакции, поэтому обратный вызов after_commit будет вызван только один раз. Вероятно, вы можете положиться на это и установить атрибут my_saved_changes из моего предложения только в том случае, если он еще не установлен (а затем сбросить его в обратном вызове after_commit):

attr_accessor :my_saved_changes

before_save :save_dirty_state
# Have this callback to execute the last:
after_commit :reset_dirty_state

def save_dirty_state
  # Note the change of assignment here ('||=' operator instead of '=')
  self.my_saved_changes ||= changes
end

def reset_dirty_state
  self.my_saved_changes = nil
end

Спасибо за ответ @GProst, но это решение не работает. Rails дважды вызывает before_save. Первый вызов назначил «грязные» изменения, но второй вызов сбросил все ранее назначенные «грязные» изменения. Посмотрев дальше, он обнаружил подобные проблемы, возникающие и у других людей, и это похоже на ошибку Rails.

gsumk 27.06.2024 17:30

Понятно, я обновил свое предложение

GProst 28.06.2024 00:05

Спасибо GProst, ответ работает как шарм.

gsumk 28.06.2024 17:14

Полагаясь на @GProst Ответ при обращении к вашему комментарию, похоже, вы могли бы взломать это вместе как:

after_commit :preserve_changes

attr_writer :my_saved_changes

# just so #my_saved_changes always returns a Hash
def my_saved_changes
  @my_saved_changes ||= {} 
end 

def preserve_changes
  self.my_saved_changes = saved_changes if saved_changes?
end

Это позволит избежать перезаписи вторичного сохранения, поскольку изменений для сохранения нет, поэтому saved_changes? будет ложным.

Если вы хотите использовать before_save, как предложено в указанном сообщении, вы можете вместо этого использовать has_changes_to_save? в качестве защиты.

Другие вопросы по теме