Reputation: 2167
I read about cast_assoc/3 here .But documentation looks confusing. I want to build single changeset for the entire association
and execute it in one transaction for update. Here are my models
;
defmodule User do
use Gallery.Web, :model
schema "users" do
field(:name, :string)
field(:occupation, :string)
has_many(:paintings, Painting)
end
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [ :name, :occupation ])
|> validate_required([:name, :occupation])
end
end
defmodule Painting do
use Gallery.Web, :model
schema "paintings" do
field(:name, :string)
belongs_to(:users, User)
end
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [ :name ])
|> validate_required([:name])
end
end
This is the data i want to build a single changeset of
data= %User{
__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
id: 4606,
name: "Test",
occupation: "Artist",
paintings: [
%Painting{
__meta__: #Ecto.Schema.Metadata<:loaded, "paintings">,
user_id: 4606,
id: 1515,
name: "philip"
},
%Painting{
__meta__: #Ecto.Schema.Metadata<:loaded, "paintings">,
user_id: 4606,
id: 1516,
name: "john"
}
]
}
Any suggestions?
Thanks
Upvotes: 1
Views: 535
Reputation: 9289
For changesets to work your data needs to be plain maps and not structs (as if you got it from params).
If you want to just insert user with multiple paintings you need to:
cast_assoc
in user changesetLike this:
data = %{
name: "Test",
occupation: "Artist",
paintings: [
%{
name: "philip"
},
%{
name: "john"
}
]
}
%User{}
|> User.changeset(data)
|> Repo.insert
If you want to also update stuff in this way, it gets more complicated. It is not clear if the list of paintings in data
should update existing paintings in place, add new ones or delete all previous ones and replace them with those in data
. I personally wouldn't recommend nested changesets for updates. https://hexdocs.pm/ecto/Ecto.Changeset.html#cast_assoc/3
UPDATE AFTER CLARIFICATION:
To update all the paintings in place you need to do two more things. You need to:
Like this:
data = %{
name: "Test",
occupation: "Artist",
paintings: [
%{
id: 1,
name: "philip"
},
%{
id: 2,
name: "john"
}
]
}
User
|> Repo.get_by(id: user_id)
|> Repo.preload(:paintings)
|> User.changeset(data)
|> Repo.update
You don't need to use Multi
. It will be one transaction. Using Repo
module once usually indicates one database operation.
All the magic happens in the paintings: [...]
. According to docs you have four cases:
- If the parameter does not contain an ID, the parameter data will be passed to changeset/2 with a new struct and become an insert operation
- If the parameter contains an ID and there is no associated child with such ID, the parameter data will be passed to changeset/2 with a new struct and become an insert operation
- If the parameter contains an ID and there is an associated child with such ID, the parameter data will be passed to changeset/2 with the existing struct and become an update operation
- If there is an associated child with an ID and its ID is not given as parameter, the :on_replace callback for that association will be invoked (see the “On replace” section on the module documentation)
You are interested in case the third case for updating in place. If you don't pass all the paintings in data
you may be also interested in fourth one.
Upvotes: 1