Как упомянуто @ steve-pallen, нет необходимости хранить какие-либо ссылки на children
в базе данных.Определение того, является ли Part
родителем или потомком, а также то, какие Part
s являются его дочерними элементами или какой Part
является его родителем, может быть полностью определено полем parent_id
.
Вы описали в своем вопросе, что каждый Part
«может иметь только одного родителя, но несколько детей».В вашем вопросе неясно, сколько уровней позволяют отношения: т.е. может ли Part
быть и родителем, и ребенком?В этом случае могут быть потенциально бесконечные уровни вложенности:
part1
|- part2
|- part3
|- part4
В этом случае part1
является родителем part2
, part2
сам является родителем part3
и т. Д.Я собираюсь предположить для моего ответа, что нет ограничений на количество вложений.
Учитывая этот случай, ваше определение схемы на 100% правильно:
belongs_to :parent, MyApp.Housing.Part
has_many :children, MyApp.Housing.Part, foreign_key: :parent_id
Я думаюосновная проблема связана с вашей функцией ревизии.Помните, что с put_assoc/3
ожидается, что все модели, на которые ссылаются parent
и children
, уже существуют в БД (см. Документацию для cast_assoc / 3 ).Для простоты я предлагаю не использовать put_assoc
или cast_assoc
, а вместо этого управлять каждой моделью по отдельности.Если вы измените свою функцию набора изменений на это (я удалил id
, так как в этом нет необходимости):
def changeset(part, params \\ %{}) do
part
|> cast(params, [:title, :level, :parent_id])
|> validate_required([:title, :level])
end
Тогда вы можете построить вложенные отношения, которые я показал выше, выполнив 4 вставки изолированно (гораздо прощерассуждать и, вероятно, в большей степени соответствовать тому, как вы будете обрабатывать обновления БД из формы или скрипта):
part1 =
MyApp.Housing.Part.changeset(%MyApp.Housing.Part{}, %{title: "part1", level: 0, parent_id: nil})
|> Repo.insert!()
part2 =
MyApp.Housing.Part.changeset(%MyApp.Housing.Part{}, %{title: "part2", level: 0, parent_id: part1.id})
|> Repo.insert!()
part3 =
MyApp.Housing.Part.changeset(%MyApp.Housing.Part{}, %{title: "part3", level: 0, parent_id: part2.id})
|> Repo.insert!()
part4 =
MyApp.Housing.Part.changeset(%MyApp.Housing.Part{}, %{title: "part4", level: 0, parent_id: part3.id})
|> Repo.insert!()
Предполагая, что мы хотим получить part2
, мы можем загрузить его вместе с его родителеми детям нравится это:
part2 = Repo.preload(part2, [:parent, :children])
# part2.parent == %MyApp.Housing.Part{title: "part1", ...}
# part2.children == [%MyApp.Housing.Part{title: "part3", ...}]
Надеюсь, это поможет!