Hmm, inputs_for is usually used with the child schema e.g. a form for an employer would contain inputs_for employee. My guess is that the _persistent_id error you’re seeing is related to the dynamically adding and removing child inputs that inputs_for supports.
This can be tricky since when the parent does not exist, you’d usually want a form that returns something like this where the parent is at the top level e.g. what a form with inputs_for would return:
employer_params = %{
"name" => "Ericsson",
"employees" => [%{"name" => "John Doe"}]
}
%Employer{}
|> cast(employer_params, [:name])
|> cast_assoc(:employees)
|> Repo.insert()
Whereas when the parent already exists, you’d want a form that returns something like this where the child is at the top level and a select input dropdown for the parent id.
employee_params = %{"name" => "Eric", "employer_id" => 1}
%Employee{}
|> cast(employee_params, [:name, :employer_id])
|> Repo.insert()
References:
Inserting associated records | Ecto Associations guide
Example: Adding a comment to a post | Ecto.Changeset.put_assoc/4 docs
Instead of changing the form struct itself, here’s an example of how you could set an assign that acts as a flag for whether the parent will be existing or new and how to save them together. Note that the code below is for a schema where a calendar has many events instead of an employer has many employees.
# inside the event form, render either a dropdown for the `calendar_id` field or a calendar form based on the boolean assign `@add_to_existing_calendar`
# this flag defaults to `true` and can be toggled by a button also within the event form `@form` via a `phx-click` binding
<%= if @add_to_existing_calendar do %>
<.input field={@form[:calendar_id]}
type="select"
label="Add to existing calendar:"
placeholder="calendar"
options={@calendar_options}
prompt="-- select a calendar for this event --"
/>
<% else %>
<.simple_form for={@calendar_form} id="calendar-form">
<.input field={@calendar_form[:name]}
type="text"
label="Add to new calendar:"
placeholder="name"
/>
</.simple_form>
<% end %>
<p>-- or --</p>
<.button type="button" phx-click="toggle_calendar_source" phx-target={@myself}>
<span :if={@add_to_existing_calendar}>Add to new calendar instead</span>
<span :if={!@add_to_existing_calendar}>Choose existing calendar instead</span>
</.button>
def update(%{event: event} = assigns, socket) do
event_changeset = Scheduling.change_event(event)
new_calendar_changeset = Scheduling.change_calendar(%Scheduling.Calendar{})
calendar_options = for cal <- Scheduling.list_calendars, do: {cal.name, cal.id}
{:ok,
socket
|> assign(assigns)
|> assign_form(event_changeset)
|> assign(:add_to_existing_calendar, true)
|> assign(:calendar_options, calendar_options)
|> assign(:calendar_form, to_form(new_calendar_changeset))}
end
def handle_event("toggle_calendar_source", _, socket) do
{:noreply, assign(socket, :add_to_existing_calendar, !socket.assigns.add_to_existing_calendar)}
end
def handle_event("save", %{"calendar" => calendar_params, "event" => event_params}, socket) do
# this works thanks to `cast_assoc(:events)` for calendars
calendar_params_with_event = Map.put(calendar_params, "events", [event_params])
case Scheduling.create_calendar(calendar_params_with_event) do
...
end
end
# creating an event tied to an existing calendar via the dropdown works out of the box with the default `"save"` event handler below
# just make sure `calendar_id` is added to `cast` for events
def handle_event("save", %{"event" => event_params}, socket) do
save_event(socket, socket.assigns.action, event_params)
end
defp save_event(socket, :edit, event_params) do
case Scheduling.update_event(socket.assigns.event, event_params) do
...
end
end






















