所以我刚开始和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
该功能性是为了使部件可以有多个孩子,但只有一个父母。这些是在这样的JSON中定义的:
{"id": 10,
"title": "Matt",
"level": 0,
"children": [],
"parent_id": null}
所以我的问题如下:
{"part":{}}
,否则引发ActionClauseError。children":["is invalid"]
。而且我不知道如何获得一个有效的密码,如果我这样做了,我可能可以找出问题所在。 我在这里可能采取了错误的方法,但是很乐意接受任何帮助。
答案 0 :(得分:0)
您需要解决的第一件事是迁移。您不希望children字段,因为那是一个has_many关系,并由child中的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
处理变更集中的子项取决于几件事。
答案 1 :(得分:0)
如@ steve-pallen所述,没有必要在数据库中存储对children
的任何引用。 Part
可以完全确定Part
是父母还是孩子,以及哪个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
,而应单独管理每个模型。如果您将changeset函数更改为此(我已删除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", ...}]
希望这会有所帮助!