Reputation: 8454
In my Phoenix app, I am getting a no function clause matching in Ecto.Changeset.change/2
error when trying to update a model with an embeds_many
relationship. I've read the docs and seen other posts about this, but I can't figure out what I'm doing wrong.
First off, here's the error:
** (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
The 'parent' model is Agenda
, and the embedded model is AgendaPage
. The models are defined as follows:
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
And the update
action from agenda_controller.ex
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
In the iex
terminal, I can access an existing agenda with MyApp.Repo.get(MyApp.Agenda, "default_agenda")
, which gives back the following record:
%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"}]}
An example of the agenda_params
that would be passed into the changeset in the controller action would look like:
%{
"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"
}
]
}
But trying to run this data through my update action produces the error. Can anyone offer some guidance?
Upvotes: 0
Views: 1086
Reputation: 4517
First off, the following does not look right:
%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"}]}
The pages
list should be a list of AgendaPage structs, not maps as you can see from my example below. I'm not sure how you ended up with that.
However, the main issue seems to be providing the id
field in your params. I had the same issue when trying to update an existing record based on your sample params above. Hoever, if you drop the id field from the params on the update, it works.
Here is a working example:
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 ($1,$2,$3,$4) ["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" = $1, "updated_at" = $2 WHERE "id" = $3 [[%{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" = $1) ["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)>
Upvotes: 0