I have a schema with a boolean field with a default value of true
. I’m using this in a LiveView, wrapped in a changeset and converted to a Phoenix.HTML.Form
with to_form/1
. As well as rendering an input for the field, there’s another part of the form that displays conditionally depending on whether the boolean field is set (<div :if={not @form[my_field].value ...>
}. This works on page load, and when the field is toggled to false
, but when I toggle it back to true
the view crashes with an argument error, because it turns out the value is now set to the string "true"
instead of the boolean true
.
I can work around this with :if={@form[:my_field].value in [false, "false"]
, but I wondered whether this is expected behaviour or a bug.
I’ve reduced it to a simple demo in a LiveBook with just a struct and changeset, which you can find in this gist. Here are the key bits:
defmodule Person do
alias Ecto.Changeset
defstruct name: "", member: true
def changeset(person, changes \\ %{}) do
Changeset.cast(
{person,
%{name: :string, member: :boolean}},
changes,
[:name, :member]
)
end
end
The changeset itself handles the casting correctly:
changeset = %Person{} |> Person.changeset()
IO.inspect(Changeset.fetch_field!(changeset, :member), label: "Default")
changeset = %Person{} |> Person.changeset(%{"member" => "false"})
IO.inspect(Changeset.fetch_field!(changeset, :member), label: "From default to false")
changeset = %Person{} |> Person.changeset(%{"member" => "true"})
IO.inspect(Changeset.fetch_field!(changeset, :member), label: "From default to true")
changeset = %Person{member: true} |> Person.changeset(%{"member" => "false"})
IO.inspect(Changeset.fetch_field!(changeset, :member), label: "From true to false")
changeset = %Person{member: false} |> Person.changeset(%{"member" => "true"})
IO.inspect(Changeset.fetch_field!(changeset, :member), label: "From false to true")
Default: true
From default to false: false
From default to true: true
From true to false: false
From false to true: true
But when I turn it into a form, it treats the toggle back to true as a change to the string "true"
:
form = %Person{} |> Person.changeset() |> to_form()
IO.inspect(form[:member].value, label: "Default")
form = %Person{} |> Person.changeset(%{"member" => "false"}) |> to_form()
IO.inspect(form[:member].value, label: "From default to false")
form = %Person{} |> Person.changeset(%{"member" => "true"}) |> to_form()
IO.inspect(form[:member].value, label: "From default to true")
form = %Person{member: true} |> Person.changeset(%{"member" => "false"}) |> to_form()
IO.inspect(form[:member].value, label: "From true to false")
form = %Person{member: false} |> Person.changeset(%{"member" => "true"}) |> to_form()
IO.inspect(form[:member].value, label: "From false to true")
Default: true
From default to false: false
From default to true: "true"
From true to false: false
From false to true: true
If it turns out to be a bug I’ll raise an issue, but it’s much more likely I’m just doing something wrong!