Embedded maps in an Ecto schema (and the associated form in Phoenix)

Breaking off a new forum thread from this comment: https://elixirforum.com/t/frustrated-with-the-flip-flops-between-atoms-and-strings-in-phoenix/58672/17 from @sodapopcan

I have a form that works like this:

Here’s a gist for the schema and form:

Apparently, there is a better way with embeds_many and fields_for? I’d really appreciate any guidance.

Not to pass the buck, but you would do well to read @LostKobrakai’s article One-to-Many LiveView Forms. It even uses an embedded schema for the (interactive) example!

Your template should end up looking something like this:

<.inputs_for :let={f_data} field={@form[:data]}>
  <.input field={f_data[:year]} label="Year" type="select" options={@year_options} />
  <.input field={f_data[:kind]} label="Year" type="select" options={@kind_options} />
  
  <.input
    field={f_data[:amount]}
    label="Amount"
    type="number"
    required={Ecto.Changeset.get_field(f_data.source, :amount) in [:income, :custom_payment]}
  />

  <.input field={f_data[:description]} label="Description" />
</.inputs_for>

Of course I’m leaving out some of the checks—I just threw in the required check as an example—but the main point is is that there should be no need to add and reference indices nor should you have to manually check for the “selected” option. All that is taken care of for you using an embedded schema with <.inputs_for />.

Some other small notes:

  • You’ll have to switch around how you’re building your year_options and kind_options. The options attribute for <.input type="select" /> takes them in the form of [{"label", :value}]

  • It’s better to pass structs to verified routes instead of the id explicitly:

    ~p"/cases/#{project}/participations/#{participation}"
    

    It probably doesn’t matter for your project but if you ever decide to use another field (like slug) for URL params, all you’d have to do is add @derive {Phoenix.Param, key: :slug} to your schema and everything else will be taken care of.

PS, there is a new way of making forms like this that I still haven’t tried since I haven’t felt the pain of this way yet.

2 Likes

I don’t see that as passing the buck at all, that’s very helpful. I only attached my existing stuff to illustrate what I was doing.

I didn’t know that about deriving params! Very helpful and something I’m used to from Django with defining get_absolute_url for every model.

Thanks for the in-depth response!

1 Like