【问题标题】:Self referencing association in Phoenix using EctoPhoenix 中使用 Ecto 的自引用关联
【发布时间】:2018-10-11 14:09:32
【问题描述】:

所以我刚刚开始摆弄 Phoenix 和 Elixir。所以我已经达到了这一点,即我正在尝试让一个工作的 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

inteded 功能是让一个部件能够有多个孩子,但只有一个父母。这些是在 JSON 中定义的,如下所示:

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

所以我的问题如下:

  • “changeset”要求传入的对象看起来像{"part":{}}否则会抛出 ActionClauseError。
  • 如上定义对象时,我收到错误children":["is invalid"]。而且我不知道如何获得有效的,如果我这样做了,我可能会找出问题所在。

我可能在这里采取了错误的方法,但很乐意接受任何帮助。

【问题讨论】:

    标签: postgresql elixir phoenix-framework ecto


    【解决方案1】:

    正如@steve-pallen 所述,没有必要在数据库中存储对children 的任何引用。确定Part 是父还是子,以及哪个Parts 是它的子或者哪个Part 是它的父,可以完全由parent_id 字段来确定。

    您在问题中描述了每个Part“只能有一个父母,但可以有多个孩子”。您的问题没有明确说明关系允许多少级别:即Part 可以既是父母又是孩子?在这种情况下,可能会有无限层的嵌套:

    part1
      |- part2
        |- part3
          |- part4
    

    在这种情况下,part1part2 的父级,part2 本身就是 part3 的父级,等等。我将假设我的答案是没有限制嵌套。

    鉴于这种情况,您的架构定义是 100% 正确的:

    belongs_to :parent, MyApp.Housing.Part
    has_many :children, MyApp.Housing.Part, foreign_key: :parent_id
    

    我认为主要问题在于您的变更集功能。请记住,对于put_assoc/3,预计parentchildren 引用的所有模型都已存在于数据库中(请参阅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", ...}]
    

    希望这会有所帮助!

    【讨论】:

    • 这几乎肯定会有所帮助。我会尽快看看。
    【解决方案2】:

    您需要解决的第一件事是迁移。您不需要 children 字段,因为这是一个 has_many 关系,并且由子项中的 parent_id 字段处理。它应该是这样的:

     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
    

    处理变更集中的孩子取决于几件事。

    • 当有孩子时,传入的有效负载是什么样的?
    • 孩子列表中会有新的孩子还是只有现有的孩子?

    【讨论】:

      猜你喜欢
      • 2018-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多