Использование Ruby-on-Rails 5.2.3 с ruby 2.5
Проблема:
Я написал в Rails внешний интерфейс конфигурации сетевого маршрутизатора, и у него есть некоторые циклические определения, напримерВот этот:
- У переадресации есть много адресов (которые пересылаются).
- У адреса есть много переадресаций (которые пересылают его).
- Адресимеет много форвардов (на которые приходят ответы от него).
Добавление: Поскольку это может показаться немного странным на первый взгляд, вот небольшая картинка: Можно видеть три различных ассоциации между двумя объектами, где «Адрес» в каждой ассоциации играет совершенно различную роль. (Но они могут менять роли или даже играть несколько функций - и в этом суть приложения: всегда генерируйте синтаксически и логически правильную конфигурацию для конечного устройства - и эта часть уже работает).
Плюс, также необходим obviousely:
- Адрес принадлежит интерфейсу.
Я построил это обычным способом:
class Forward < ApplicationRecord
has_many :addresses, dependent: :nullify
belongs_to :remoteip, class_name: 'Address'
belongs_to :responding, class_name: 'Address', optional: true
end
class Address < ApplicationRecord
belongs_to :interface
belongs_to :forward, optional: true
has_many :remoteip_forwards, class_name: 'Forward',
foreign_key: :remoteip_id, dependent: :destroy
has_many :responding_forwards, class_name: 'Forward',
foreign_key: :responding_id, dependent: :nullify
end
Тогдав базе данных имеются ограничения, так как объекты не должны быть сиротами, в этом случае:
- address.interface_id NOT NULL
Тогда у меня есть модель проектирования верхнего уровня, который связывает воедино все остальные модели посредством соответствующих ассоциаций, так что я могу взять один дизайн и получить все вещи как одно дерево.
Пока все это работает просто отлично. Но когда я пытаюсь скопировать весь дизайн (с самоцветом deep_clone
), при сохранении некоторые довольно сложных дизайнов дают мне NotNullViolation
.
Анализ:
Журналы показывают, что Rails действительно сохраняет некоторые записи Address дважды : он делает INSERT
с действительными forward_id
, но пустыми interface_id
, и только позже это делаетUPDATE
для действительного interface_id
, все в рамках транзакции. И очевидно, что это снимает ограничение NOT NULL
, и действие завершается неудачно (при удалении ограничения копия выполняется успешно).
Я бы понял это, если бы фактический проект действительно содержал циклическую ссылку (потому что тогданет другого способа сохранить его), но нет ни одного ! Только логически возможно создавать циклические ссылки с этими моделями.
Я не смог найти надлежащую документацию о том, как Rails обрабатывает такие (потенциально) циклические вещи. inverse_of
ничего не помогает (но этого и следовало ожидать, так как мои имена четко определены).
Когда я использую autosafe: false
в проблемных ассоциациях, проблема решается, и Rails получает деревосохранены - но затем записи, на которые не ссылаются дважды, отсутствуют в копии (это не удивительно).
Таким образом, кажется, что код Rails, который обходит дерево ассоциаций для поиска и сохранения записей, не является полностью расширенными слишком рано прибегает к записи записи дважды (возможно, для повышения производительности?).
Как лучше всего к этому подойти?
- К сожалению, ограничения
NOT NULL
не являютсяDEFERRABLE
в postgresql. Жаль, что это может показаться самым очаровательным решением. - использовать
autosave=false
, пройтись по дереву ассоциаций пешком, чтобы найти правильную последовательность и сохранить каждую запись отдельно? Нет, спасибо. - Разметить ассоциации как-то иначе? Как? (У меня уже есть рабочий код, который преобразует все данные для целевого устройства, и он хорошо работает с текущим макетом.)
- Откажитесь от ограничений NOT NULL? Хм ...
- Или в Rails есть где-то ручка, которую можно настроить?