I have two models called “inspection” and “equipment”.
Their schemas look like this:
schema "equipments" do
field :name, :string
has_many :inspections, MyApp.Inspection
embeds_many :metrics, MyApp.Metric,
on_replace: :delete
timestamps(type: :utc_datetime)
end
schema "inspections" do
has_many :checks, MyApp.Check
belongs_to :equipment, MyApp.Equipment
timestamps(type: :utc_datetime)
end
I’m working on creating a form that would allow users to submit inspections for an equipment.
I’ve already handled the part where the inspection’s checks are rendered, validated, and submitted.
I’m now working on adding another section to the same form which would allow the user to input values for the metrics that belong to the equipment being inspected. I’m struggling with this piece at the moment. The input fields render fine, and the validations run i expect them to, but when the validation on a “metric” fails, Phoenix raises the following error message:
** (ArgumentError) could not generate inputs for :checks from MyApp.Equipment.
Check the field exists and it is one of embeds_one, embeds_many, has_one, has_many, belongs_to or many_to_many
I’ll paste some code in the thread to show what my current approach looks like.
I’d really appreciate your thoughts if you’ve come across this before, or if you think i’m doing something in a weird way <3
defmodule MyApp.InspectionLive.FormComponent do
use MyAppWeb, :live_component
@impl true
def render(assigns) do
~H"""
<div>
<.header>
<%= "#{@title}" %>
</.header>
<.simple_form
for={@form}
id="form-submission-form"
phx-target={@myself}
phx-submit="save"
>
<input type="hidden" name="inspection[equipment_id]" value={@equipment.id} />
<div id="checks">
<.inputs_for :let={check} field={@form[:checks]} id={"inspection"}>
<%= case Firedepartmentmanagement.Forms.Element.supported_type_for(check[:type].value) do %>
<CheckComponent.render
...
index={check.index}
/>
</.inputs_for>
</div>
<div id="equipment-metrics">
<h3>equipment metrics</h3>
<input type="hidden" name="equipment[name]" value={@equipment.name} />
<%= for {equipment_metric, index} <- Enum.with_index(@equipment.metrics) do %>
<div class="flex space-x-1">
<input type="hidden" name={"equipment[metrics][#{index}][id]"} value={equipment_metric.id} />
<%= equipment_metric.name %>
<input
type="text"
name={"equipment[metrics][#{index}][value]"}
value={equipment_metric.value}
/>
</div>
<% end %>
</div>
<:actions>
<.button phx-disable-with="Saving...">Submit inspection</.button>
</:actions>
</.simple_form>
</div>
"""
end
@impl true
def update(%{inspection: inspection} = assigns, socket) do
{
:ok,
socket
|> assign(assigns)
|> assign_new(
:form,
fn ->
to_form(Forms.change_inspection(inspection))
end
)
}
end
def handle_event("save", params, socket) do
save_inspection(socket, socket.assigns.action, params)
end
defp save_inspection(socket, :new, %{"inspection" => inspection_params, "equipment" => equipment_params} = params) do
case Forms.create_inspection(params) do
{:ok, inspection} ->
notify_parent({:saved, inspection})
{:noreply,
socket
|> put_flash(:info, "Submitted inspection successfully")
|> push_patch(to: socket.assigns.patch)}
{:error, %Ecto.Changeset{} = changeset} ->
as_form = to_form(changeset)
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end
Check out inputs_for, it is made for things like this.
I’d suggest sharing what your IO.inspect
returns for socket.assigns.form
and socket.assigns.inspection
as well as the code for Forms.change_inspection
.