Hey all,
I am having some trouble understanding how change sets with nested data and forms work together.
How would I make the following example work?
I have 4 schemas:
- Template: has a content and name fields that are text.
- RequiredVariable: has “label” field and variable_name field, and a template_id. (Many to 1 with template)
- TemplateInstance: has a template_id field
- VariableValue: has a template_instance_id, a required_variable_id, and “value” fields.
What I want to happen is that when a user is making a new TemplateInstance, they pick a Template name from a drop down, then a form field for each RequiredVariable appears so they can create VariableValue’s attached to the TemplateInstance.
Below is what I have so far. Basicly, I try and dynamicly add the variable values to the changeset for the TemplateInstance. But I’m not sure how the incoming parameters from the form are meant to map back to the changeset.
The way I have it set up right now, whenever a Variable value gets set it wipes away the other ones .
I assume I am not mapping things properly in the changeset but I don’t know what I am missing.
Any help would be great. Thanks!
def render(assigns) do
~H"""
<button phx-click={CoreComponents.show_modal("add_instance")}> <.icon_plus></.icon_plus> </button>
<.modal id="add_instance">
<div class="content">
<.simple_form :let={f} for={@new_instance} phx-change="validate">
<.error :if={@new_instance.action}>
Oops, something went wrong! Please check the errors below.
</.error>
<.input field={f[:template_id]} type="select" label="Template" options={@templates} />
<.inputs_for :let={f_var} field={f[:variables]}>
<div class="flex gap-x-5">
<.input field={f_var[:value]}
type="text"
label={Map.get(@variables, f_var[:var_id].value).label} />
</div>
</.inputs_for>
<:actions>
<.button>Save</.button>
</:actions>
</.simple_form>
</div>
</.modal>
"""
end
def mount(_params, session, socket) do
template_options =
[
{"Choose Template...", ""}
| Fawkes.Templates.active!()
|> Enum.map(fn q -> {q.name, q.id} end)
]
{new_instance, vars} = new_instance(%{}, Map.get(session, "project_id"))
socket =
socket
|> assign(:project_id, Map.get(session, "project_id"))
|> assign(:new_instance, new_instance)
|> assign(:vars, vars)
|> assign(:templates, template_options)
{:ok, socket, layout: false}
end
def handle_event("validate", params, socket) do
{changeset, vars} = new_session(params, socket.assigns.project_id)
socket = assign(socket, :new_instance, changeset)
{:noreply, assign(socket, :vars, vars)}
end
defp new_instance(params, project_id) do
new_params =
Map.get(params, "session", %{})
|> Map.put("project_id",project_id)
TemplateInstance.changeset(%TemplateInstance{}, new_params)
|> fill_vars(new_params)
end
def fill_vars(changeset, params) do
instance_id = Ecto.Changeset.get_field(changeset, :instance_id)
if is_nil(instance_id) or instance_id == "" do
{changeset, %{}}
else
query = from q in TemplateInstance, preload: :variables
instance = Repo.get(query, instance_id)
if is_nil(instance) do
{Ecto.Changeset.add_error(changeset, :instance_id, "Doesn't exist"), %{}}
else
context_changes =
instance.required_vars
|> Enum.map(fn var ->
VariableValue.changeset(%VariableValue{}, %{"var_id" => var.id})
end)
changeset = Ecto.Changeset.put_assoc(changeset, :vars, vars)
keys =
instance.vars
|> Map.new(fn var -> {var.id, var} end)
{changeset, keys}
end
end
end