Wondering if anyone has a good suggestion on how to store ‘extra meta data’ for a form?
I think I can best describe my conundrum with an example.
To start with, I have a Config
schema with an embeds_many
for config_fields.
defmodule MyApp.Config do
use MyAppWeb, :schema
defmodule ConfigField do
use Ecto.Schema
embedded_schema do
field(:label, :string)
field(:value, :string)
end
def changeset(config_field, attrs \\ %{}) do
config_field
|> cast(attrs, [
:label,
:value
])
end
end
schema "configs" do
field(:label, :string)
embeds_many(:config_fields, ConfigField, on_replace: :delete)
timestamps()
end
def changeset(config, attrs \\ %{}) do
config
|> cast(attrs, [
:label,
])
|> cast_embed(:config_fields)
end
end
In my LiveView form, I have an inputs_for
for the config_fields
. My value is determined from a select drop down, but my options
are specific to each config_field
. They also aren’t fixed.
<.inputs_for :let={cf} field={@form[:config_fields]}>
<.input field={cf[:value]} type="select" options={@UNSURE_WHERE_TO_GET_FROM} />
</.inputs_for>
The options are from another schema, where a user can change these options as they please.
defmodule MyApp.TemplateConfigFields do
use MyAppWeb, :schema
schema "template_config_fields" do
field(:label, :string)
field(:options, {:array, :string})
timestamps()
end
def changeset(template_config_field, attrs \\ %{}) do
template_config_field
|> cast(attrs, [
:label,
:options
])
|> unique_constraint(:label)
end
def get_all() do
query = from(tco in __MODULE__)
MyRepo.all(query)
end
end
In the handle_params
of my LiveView, the form is created like the following
changeset = Config.changeset(%Config{})
socket = assign(socket, form: to_form(changeset))
And then I add the config fields based off the template config fields.
config_fields = TemplateConfigFields.get_all()
update(socket, :form, fn %{source: changeset} ->
changeset = Ecto.Changeset.put_embed(changeset, :config_fields, config_fields)
to_form(changeset)
end)
I have 3 paths that I’ve explored for displaying the relevant options
.
- Using cf.source.data.options
Because theConfig
is an empty struct, after the first ‘validate’ the form with theput_embed
of ConfigFields structs are lost. This means theConfigField
becomes an empty struct with thelabel
andvalue
being reflect as a ‘changes’ in the form.
### BEFORE
# cf
%Phoenix.HTML.Form{
source: #Ecto.Changeset<action: nil, changes: %{}, errors: [],
data: #MyApp.Config.ConfigField<>, valid?: true>,
data: %MyApp.Config.ConfigField{
id: nil,
label: "select menu",
value: nil,
options: ["asd", "asdsa", "sadsad"]
},
action: nil,
params: %{"_persistent_id" => "3"},
}
# cf.source.data.options
["asd", "asdsa", "sadsad"]
After validate is called on the form
# cf
%Phoenix.HTML.Form{
source: #Ecto.Changeset<
action: :insert,
changes: %{label: "select menu", value: "asd"},
errors: [],
data: #MyApp.Config.ConfigField<>,
valid?: true
>,
data: %MyApp.Config.ConfigField{
id: nil,
label: nil,
value: nil,
options: nil
},
action: :validate,
params: %{
"_persistent_id" => "3",
"label" => "select menu",
"value" => "asd"
},
}
# cf.source.data.options
nil
- Persisting the
options
as a hidden value
Usinginput_value(cf, :options)
won’t work because the ‘options’ are converted into a string without any separators.
embedded_schema do
field(:label, :string)
field(:value, :string)
field(:options, {:array, :string}, virtual: true)
end
- Using a seperate variable in the assigns
I’ve considered using a map with the key being ‘name’ of theconfig_field
and teh value being the options,options={@config_field_options[cf[:name].value}]
, but that feels a bit messy?