Мне было трудно отлаживать это, и я собирался попросить о помощи. Но мне удалось определить причину, и я хотел бы поделиться своими выводами на случай, если кто-то еще столкнется с той же проблемой. [И, возможно, кто-то может объяснить, почему это работает так, как работает]
Настраивать
Допустим, у меня есть два монгоидных документа, Customer
и Order
с отношением 1:n. Кроме того, Customer
имеет обратный вызов after_save
для синхронизации изменений документа с внешним API:
class Customer
include Mongoid::Document
has_many :orders
after_save do
puts "synchronizing customer" # <- not my actual code
end
end
class Order
include Mongoid::Document
belongs_to :customer
validates_presence_of :customer
end
Все работает так, как ожидалось. Создание и обновление клиентов приводит к срабатыванию обратного вызова after_save
, а создание заказов — нет.
Изменять
Через некоторое время Customer
понадобится новое поле со значением по умолчанию:
class Customer
# ...
field :premium, type: Boolean, default: false
end
Проблема
Но вдруг все становится странным. После этого изменения создание (или обновление) заказов также приводит к сохранению клиента! (Я заметил это из-за своих логов — синхронизация шла без видимой причины)
c = Customer.last
c.orders.create
synchronizing customer # <- what the?
#=> #<Order _id: 575a995aab265d730b8bddba ...>
Как ни странно, это происходит только для существующих клиентов и только один раз.
Причина
Долгая и утомительная сессия отладки показала, что отношение Order
belongs_to
имеет флаг autosave
:
Order.relations['customer'].autosave?
#=> true
Это было включено проверкой присутствия и фактически примечаниями Mongoid в documentation это небрежно:
Обратите внимание, что функция автосохранения будет автоматически добавлена к отношению при использовании
accepts_nested_attributes_for
или проверке наличия отношения.
Но autosave
сохраняет документ только в том случае, если он был изменен, так откуда взялось изменение? Судя по всему, мое новое поле premium
со значением по умолчанию внесло небольшое изменение:
c = Customer.first # a customer from before the change without "premium" attribute
c.changed?
#=> true
c.changes
#=> {"premium"=>[nil, false]}
Решение
В конце концов, исправление было довольно тривиальным. Мне просто пришлось явно отключить автосохранение для моего отношения belongs_to
:
class Order
include Mongoid::Document
belongs_to :customer, autosave: false
validates_presence_of :customer
end
Открытые вопросы
Но остается вопрос: почему проверка «присутствия» Mongoid включает автосохранение? Как это может быть желаемым поведением по умолчанию? Пожалуйста, просветите меня.