I couldn't find info about updating models with has_many associations using nested forms with dinamically added associated model's fields, all I could find was about inserting new models.
But when we update the parent model we sometimes need to take care about preserving Id's of associated children models.
Given we have two models, having has_many relationship like this:
parent.ex
defmodule MyApp.Parent do
use MyApp.Web :model
schema "parents" do
field :name, :string
has_many :children, MyApp.Child
timestamps
end
def changeset(parent, params \\ %{}) do
parent
|> cast(params, [:name])
|> validate_required([:name])
|> cast_assoc(:children)
end
end
child.ex
defmodule MyApp.Child do
use MyApp.Web :model
schema "children" do
field :name, :string
belongs_to :parent, MyApp.Parent
timestamps
end
def changeset(child, params \\ %{}) do
child
|> cast(params, [:name])
|> validate_required([:name])
end
end
And nested form for the Parent with inputs_for for it's children like this:
form.html.eex
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= label f, :name, class: "control-label" %>
<%= text_input f, :name, class: "form-control" %>
<%= error_tag f.source, :name %>
</div>
<%= inputs_for f, :children, [append: [MyApp.Child.changeset(%Child{})]], fn fc -> %>
<div class="form-group">
<%= label fc, :name, class: "control-label" %>
<%= text_input fc, :name, class: "form-control" %>
<%= error_tag fc.source, :name %>
</div>
<div class="form-group">
<%= checkbox fc, :delete, class: "delete-checkbox" %>
</div>
<% end %> <!-- end the nested children -->
<%= submit "Submit" %>
<% end %>
And with some javascript help it also is allowed to add more children to the form.
The question:
If I want to preserve children Id's on parent's update action do I need to add hidden Id's form fields to all existing children and add them to the allowed fields to cast function call in MyApp.Child.changeset? Or is there another way to force Ecto to update old children instead of removing old ones and creating new with new Id's?
cast_assoc
, if theid
is present and valid (exists and belongs to the parent), the record will be updated, otherwise a new record will be inserted. See hexdocs.pm/ecto/Ecto.Changeset.html#cast_assoc/3. – Dogbert