Error with `Phoenix.HTML.Form` and `Ecto.Enum`

The TL;DR is that the initial selected value of an Ecto.Enum field seems to come through as a string when re-reselected in a dropdown.

So I have an enum field in my schema:

schema "transformations" do
  # ...
  field :name, Ecto.Enum, [:size_to_fill, :size_to_fit, ...], default: :size_to_fill
  # ...

I set the options in my LiveView with something equivalent to this:

transformation_name_options =
  |> transformation_name ->
    display_name =
      |> Atom.to_string()
      |> String.replace("_", " ")
      |> String.capitalize()

    {display_name, transformation_name}

{:ok, assign(socket, :transformation_name_options, transformation_name_options)}

This is a nested one-to-many association so in my template I have:

def render(assigns) do
  <.inputs_for let={f_transformations} field={@form[:transformations]}>
    <.input field={f_transformations[:name]} label="Name" type="select" options={@transformation_name_options} />

    <%= case f_transformations[:name] do %>
      <% :size_to_fill -> %>
        <% # fields for :size_to_fill #>

      <% # more matches on other transformations %>
   <% end %>

It’s important to note that I can add additional transformations that all get this dropdown.

So the template loads normally with :size_to_fill as the default. I can then choose other transformations from the dropdown and they show their fields correctly. As soon as I try and select :size_to_fill again, I get an error that it can’t match on "size_to_fill", ie, the string version. If I save the form with a different transformation then build a new one, I now also error for the one that is now being loaded from the db. Does that makes sense? It’s a bit hard to explain. Basically any dropdown option that is initially loaded into the form works on first load but then is read as a string from then on when selected again.

Has anyone run into this? I can fix it by ensuring f_transformations[:name] is cast to an atom in the case statement, but it’s still strange. I’m prrrretty sure it’s nothing I’m doing as I’ve been over everything several times… but possibly.


erlang 26.0.2
elixir 1.14.5 (ya, I should really upgrade)

# ...
{:phoenix, "~> 1.7.2"},
{:phoenix_live_view, "~> 0.19"},
{:phoenix_html, "~> 3.3"},
{:phoenix_ecto, "~> 4.4"},
{:ecto_sql, "~> 3.10.1"},
# ...

Yeah, I ran into something similar here in another post.

I think it just comes down to the fact that atoms don’t exist in HTTP/JS/browser/etc. so they naturally get serialized into strings on their way to the client side. But the reverse doesn’t happen automatically because LiveView on the server can’t differentiate between strings that were always strings and strings that were once atoms.

Oh right, it didn’t even occur to me that the two would be related. It’s weird, though, I didn’t solve that same radio button problem I had by looking for a string or atom. I’m honestly not even sure exactly what I did. I’m looking at my code for it right now and still unsure, lol.

As for this problem with the dropdown, it still doesn’t make sense to me because it only happens with the selected ones. I can switch around between anything else in the dropdown to my heart’s content without issue. I would have thought that because it’s coming from the form struct that it gets properly cast to an atom, although I really have no good reason for thinking that.

Anyway, thanks for your response! I’m still curious but don’t have the appetite to really dig in as I’m working on a monster form that is taking forever.