Ассоциация самоссылки в Фениксе с использованием Ecto - PullRequest
0 голосов
/ 10 октября 2018

Так что я только начал возиться с Фениксом и Эликсиром.Таким образом, я достиг точки, где я пытаюсь получить рабочую конечную точку rest-api для работы с необходимым JSON.

Итак, у меня есть этот модуль:

defmodule MyApp.Housing.Part do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :integer, []}
  schema "parts" do
    field :level, :integer
    field :title, :string
    belongs_to :parent, MyApp.Housing.Part
    has_many :children, MyApp.Housing.Part, foreign_key: :parent_id
    timestamps()
  end

  def changeset(part, params \\ %{}) do
    part
    |> cast(params, [:title, :level, :id, :parent_id])
    |> put_assoc(:children, required: false)
    |> put_assoc(:parent, required: false)
    |> validate_required([:title, :level, :id])
  end
end

И модуль, в котором создается таблица

 defmodule MyApp.Repo.Migrations.CreateParts do
  use Ecto.Migration

  def change do
    create table(:parts, primary_key: false) do
      add :id, :integer, primary_key: true
      add :title, :string
      add :level, :integer
      add :parent_id, references(:parts)
      add :children, references(:parts)
      timestamps()
    end

    create index(:parts, [:children])
    create index(:parts, [:parent_id])
  end
end

Целевая функциональность предназначена для того, чтобы деталь моглаесть несколько детей, но только один родитель.И они определены в JSON следующим образом:

{"id": 10,
    "title": "Matt",
    "level": 0,
    "children": [],
    "parent_id": null}

Поэтому моя проблема заключается в следующем:

  • "changeset" требует, чтобы входящий объект выглядел как {"part":{}} в противном случаеActionClauseError вызывается.
  • При определении объекта, как указано выше, я получаю ошибку children":["is invalid"].И я не могу понять, как получить действительный, если бы я сделал, я, вероятно, мог бы выяснить проблему.

Я мог бы использовать здесь неправильный подход, но с радостью принял бы любую помощь.

Ответы [ 2 ]

0 голосов
/ 11 октября 2018

Как упомянуто @ 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", ...}]

Надеюсь, это поможет!

0 голосов
/ 10 октября 2018

Первое, что вам нужно исправить, - это миграция.Вам не нужно поле children, поскольку это отношение has_many, которое обрабатывается полем parent_id в children.Это должно выглядеть так:

 defmodule MyApp.Repo.Migrations.CreateParts do
  use Ecto.Migration

  def change do
    create table(:parts, primary_key: false) do
      add :id, :integer, primary_key: true
      add :title, :string
      add :level, :integer
      add :parent_id, references(:parts)

      timestamps()
    end

    create index(:parts, [:parent_id])
  end
end

Работа с детьми в наборе изменений зависит от пары вещей.

  • Как выглядит входящая полезная нагрузка, когда есть дети?
  • Будут ли новые дети в списке детей или только существующие дети?
...