So, I have an embedded schema, it’s pretty large – I am doing the entire state of the app in one schema called State
. It is made up of two embed_one’s, and the first one is Control
which has some parameters for lights:
embedded_schema do
field :light_top, :float, default: 0.0 # brightness
field :light_bottom, :float, default: 0.0 # brightness
field :light_top_on, :boolean, default: false
field :light_bottom_on, :boolean, default: false
...
def changeset(%__MODULE__{} = ctrl, params \\ %{}) do
ctrl
|> cast(params, @cast)
|> validate_required([])
end
Note, I have checked @cast
a dozen times, it is correct and includes all these fields. And the changeset for State
has the proper |> cast_embed(:ctrl, with: &MyApp.Control.changeset/2)
.
Then in my LiveView controller, I handle the change event:
def handle_event("change", %{"state" => state} = _params, socket) do
MyApp.State.changeset(socket.assigns.form.data, state)
|> Map.put(:action, :insert)
|> form_to(socket)
|> noreply()
end
defp form_to(changeset, socket) do
assign(socket, :form, to_form(changeset))
end
def noreply(socket) do
{:noreply, socket}
end
In my view components, I have a controlbar component which is passed @form[:ctrl]
as @ctrl
. (I thought it was FormData
at first, but it is actually just a Form
), That gets passed down to the lighting controls of the control bar:
attr :ctrl, Phoenix.HTML.Form
defp ctrlbar_lighting(assigns) do
~H"""
<div class="flex flex-row gap-4 items-center">
<.light_button field={@ctrl[:light_bottom_on]} title="Bottom Light On/Off" />
<.input type="number" field={@ctrl[:light_bottom]} title="Bottom Light Brighness" />
<.light_button field={@ctrl[:light_top_on]} title="Top Light On/Off" />
<.input type="number" field={@ctrl[:light_top]} title="Top Light Brighness" />
</div>
"""
end
attr :title, :string
attr :field, Phoenix.HTML.FormField
defp light_button(assigns) do
~H"""
<label class="button rounded pt-2 h-10 w-12 mt-2 text-center bg-slate-800" title={@title}>
<.input type="checkbox" field={@field} style="display: none;" />
<.icon name="hero-light-bulb" class={light_color(@field.value)} />
</label>
"""
end
defp light_color(on_state) do
on_state |> dbg() # "false" String? HOW!?
if on_state do
"text-yellow-500"
else
"text-white"
end
end
It almost works except for some odd behaviors. One in particular is the comment I made above… when I cycle through clicks of a light button, the field value always returns true
when on as it should, but returns "false"
(a string!) when off, even though I am casting. How is that even possible? I point out that when it is off, that’s the default, so the changeset shows no changes for that state. Still I can’t fathom why I am getting a string.