Phoenix/Ecto - 更新 embeds_many 记录时发生变更集错误

Phoenix/Ecto - changeset error when updating embeds_many record

在我的 Phoenix 应用程序中,尝试更新具有 embeds_many 关系的模型时出现 no function clause matching in Ecto.Changeset.change/2 错误。我已经阅读了文档并看到了关于此的其他帖子,但我无法弄清楚我做错了什么。

首先,这是错误:

** (FunctionClauseError) no function clause matching in Ecto.Changeset.change/2
    (ecto) lib/ecto/changeset.ex:307: Ecto.Changeset.change(%{"content" => "<p>Nice to see you</p>", "duration" => 15, "id" => "93387d2d-a6ed-4902-911f-4dc1525aca2b"}, %{})
    (ecto) lib/ecto/changeset/relation.ex:196: Ecto.Changeset.Relation.on_replace/2
    (ecto) lib/ecto/changeset/relation.ex:299: Ecto.Changeset.Relation.reduce_delete_changesets/5
    (ecto) lib/ecto/changeset.ex:691: Ecto.Changeset.cast_relation/4
    (myapp) web/models/agenda.ex:20: MyApp.Agenda.changeset/2

'parent'型号为Agenda,嵌入式型号为AgendaPage。模型定义如下:

agenda.ex

defmodule MyApp.Agenda do
  use MyApp.Web, :model

  @primary_key {:id, :string, []}
  @derive {Phoenix.Param, key: :id}
  schema "agenda" do
    field :name, :string
    embeds_many :pages, MyApp.AgendaPage, on_replace: :delete
  end

  def changeset(struct, params \ %{}) do
    struct
    |> cast(params, [:name])
    |> cast_embed(:pages)
    |> validate_required([:name])
  end
end

agenda_page.ex

defmodule MyApp.AgendaPage do
  use MyApp.Web, :model

  embedded_schema do
    field :content, :string
    field :duration, :integer
  end

  def changeset(struct, params \ %{}) do
    struct
    |> cast(params, [:content, :duration])
  end
end

以及来自 agenda_controller.ex

update 操作
def update(conn, %{"id" => id, "agenda" => agenda_params}) do
  agenda = Repo.get!(Agenda, id)
  changeset = Agenda.changeset(agenda, agenda_params)

  case Repo.update(changeset) do
    {:ok, agenda} ->
      json conn, %{status: "ok", agenda: agenda}
    {:error, changeset} ->
      errors = parse_errors(changeset)
      IO.inspect errors
      json(conn |> put_status(400), %{status: "error", message: "Failed to update Agenda", errors: errors})
  end
end

iex 终端中,我可以使用 MyApp.Repo.get(MyApp.Agenda, "default_agenda") 访问现有议程,返回以下记录:

%MyApp.Agenda{__meta__: #Ecto.Schema.Metadata<:built, "agenda">,
 id: "default_agenda", name: "Default Agenda",
 pages: [%{"content" => "<p>This is the default agenda</p>", "duration" => 10,
"id" => "0849862a-0794-4466-88a3-6052da360ca0"},
%{"content" => "<p>Nice to see you</p>", "duration" => 15,
"id" => "93387d2d-a6ed-4902-911f-4dc1525aca2b"}]}

将在控制器操作中传递到变更集中的 agenda_params 示例如下所示:

%{
  "id" => "default_agenda",
  "name" => "Default Agenda",
  "pages" => [
    %{
      "content" => "<p>foo</p>",
      "duration" => 10,
      "id" => "0849862a-0794-4466-88a3-6052da360ca0"
    },
    %{
      "content" => "<p>bar</p>",
      "duration" => 15,
      "id" => "93387d2d-a6ed-4902-911f-4dc1525aca2b"
    }
  ]
}

但是尝试通过我的更新操作 运行 此数据会产生错误。谁能提供一些指导?

首先,以下内容看起来不对:

%MyApp.Agenda{__meta__: #Ecto.Schema.Metadata<:built, "agenda">,
 id: "default_agenda", name: "Default Agenda",
 pages: [%{"content" => "<p>This is the default agenda</p>", "duration" => 10,
"id" => "0849862a-0794-4466-88a3-6052da360ca0"},
%{"content" => "<p>Nice to see you</p>", "duration" => 15,
"id" => "93387d2d-a6ed-4902-911f-4dc1525aca2b"}]}

pages 列表应该是 AgendaPage 结构的列表,而不是地图,正如您在下面的示例中看到的那样。我不确定你是怎么结束的。

但是,主要问题似乎是在您的参数中提供 id 字段。尝试根据上面的示例参数更新现有记录时,我遇到了同样的问题。但是,如果您在更新时从参数中删除 id 字段,它就可以工作。

这是一个工作示例:

iex(4)> {:ok, ag} = Repo.insert Agenda.changeset(%Agenda{id: "default_agenda"}, %{name: "Default Agenda"})
[debug] QUERY OK db=9.7ms
INSERT INTO "agendas" ("id","name","inserted_at","updated_at") VALUES (,,,) ["default_agenda", "Default Agenda", {{2017, 7, 21}, {23, 43, 46, 739178}}, {{2017, 7, 21}, {23, 43, 46, 747000}}]
{:ok,
 %Playground.Demo.Agenda{__meta__: #Ecto.Schema.Metadata<:loaded, "agendas">,
  id: "default_agenda", inserted_at: ~N[2017-07-21 23:43:46.739178],
  name: "Default Agenda", pages: [],
  updated_at: ~N[2017-07-21 23:43:46.747000]}}
iex(5)> Repo.update Agenda.changeset(ag, %{
...(5)>   "name" => "Default Agenda",
...(5)>   "pages" => [
...(5)>     %{
...(5)>       "content" => "<p>foo</p>",
...(5)>       "duration" => 10,
...(5)>       "id" => "0849862a-0794-4466-88a3-6052da360ca0"
...(5)>     },
...(5)>     %{
...(5)>       "content" => "<p>bar</p>",
...(5)>       "duration" => 15,
...(5)>       "id" => "93387d2d-a6ed-4902-911f-4dc1525aca2b"
...(5)>     }
...(5)>   ]
...(5)> }
...(5)> )
[debug] QUERY OK db=15.7ms
UPDATE "agendas" SET "pages" = , "updated_at" =  WHERE "id" =  [[%{content: "<p>foo</p>", duration: 10, id: "e7b5b7d9-9308-43f0-8bc7-c5956640772b"}, %{content: "<p>bar</p>", duration: 15, id: "1c668d06-5c60-4a4d-a052-43520597162d"}], {{2017, 7, 21}, {23, 44, 23, 752892}}, "default_agenda"]
{:ok,
 %Playground.Demo.Agenda{__meta__: #Ecto.Schema.Metadata<:loaded, "agendas">,
  id: "default_agenda", inserted_at: ~N[2017-07-21 23:43:46.739178],
  name: "Default Agenda",
  pages: [%Playground.Demo.AgendaPage{content: "<p>foo</p>", duration: 10,
    id: "e7b5b7d9-9308-43f0-8bc7-c5956640772b"},
   %Playground.Demo.AgendaPage{content: "<p>bar</p>", duration: 15,
    id: "1c668d06-5c60-4a4d-a052-43520597162d"}],
  updated_at: ~N[2017-07-21 23:44:23.752892]}}
iex(6)> Repo.get Agenda, "default_agenda"
[debug] QUERY OK source="agendas" db=7.2ms
SELECT a0."id", a0."name", a0."pages", a0."inserted_at", a0."updated_at" FROM "agendas" AS a0 WHERE (a0."id" = ) ["default_agenda"]
%Playground.Demo.Agenda{__meta__: #Ecto.Schema.Metadata<:loaded, "agendas">,
 id: "default_agenda", inserted_at: ~N[2017-07-21 23:43:46.739178],
 name: "Default Agenda",
 pages: [%Playground.Demo.AgendaPage{content: "<p>foo</p>", duration: 10,
   id: "e7b5b7d9-9308-43f0-8bc7-c5956640772b"},
  %Playground.Demo.AgendaPage{content: "<p>bar</p>", duration: 15,
   id: "1c668d06-5c60-4a4d-a052-43520597162d"}],
 updated_at: ~N[2017-07-21 23:44:23.752892]}
iex(7)>