0
votes

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?

1
Does passing the ids in a hidden field work for you? According to the docs of cast_assoc, if the id 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
I'm still pondering over this and what bothers me is I've never seen an example using hidden fields approach, and I've looked through many of them. Nevertheless, I've looked into docs by your link and it seems that cast_assoc takes care about protecting from accidental updating other's model children which is good.AndreyKo
@Dogbert: It appears that at least in my installation (Phoenix 1.3.0-rc.2, Ecto 3.2.3) hidden Id fields magically appeared in the post edit form, so there is no need to add these fields manually to the form template, they are already there. And yes, the Id's of comments persist after updating. I tryed to figure out who generates these fields for me but haven't found so far.AndreyKo

1 Answers

0
votes

Yes, you do need to add hidden fields for the IDs or it will be treated as an insert operation. If you send in the ID it will be an update.

You do NOT need to add the :id field to the cast in the child's changeset.