Autogenerate form in a loop

I wonder if is there a way to auto generate form inputs in a loop.
I have an embedded schema with lot of checkbox fields and I I did this like that:
pe is the struct for this model passed as variable

  <%= inputs_for f, :protective_equipment_value, fn p -> %>
    <ul>
      <%=  for box <- Map.keys(@pe) do %>
        <li>
          <%= checkbox p, box %>
          <%= label p, box %>
          <%= error_tag p, box %>
        </li>
      <% end %>
    </ui>
  <% end %>

The only problem is that i have an additional id field which I can probably get rid of , and a second struct: true in this map - this needs a guard.
Is there maybe an official/less hacky way to do this??

Should either be an inputs_for for each value inside this rather than one outside, or need to change the ID to put in a unique id for each grouping.

Structs are just Maps with a special :__struct__ key pointing to the struct module it comes from.

My guess is that since Map.keys(struct) will return :__struct__ as one of the keys, you are generating an extra value in this form you don’t want.

Furthermore, the to_html helper is probably converting :__struct__ to "struct" and the module name (assuming it’s something like ProtectiveEquipmentValue) to a value of true when producing the form fields.


There are a few ways to make this easier for what you are trying to do. One I’ve used before is to define all defaults for structs in a module value, and create a keys function as well as a defstruct, like such:

defmodule ProtectiveEquipmentValue do
  @defaults [my: nil, keys: nil, with: "default values"]
  defstruct @defaults
  def keys() do: @defaults |> Map.keys
end

Then in your form you can just loop over ProtectiveEquipmentValue.keys() instead.

Re-reading your description, you also have an extra unwanted :id field, as well as the :__struct__ one. It sounds like you’re using Map.keys/1 to extract map keys from an Ecto.Schema struct. You probably want to define a special list of form-friendly schema values somewhere, and iterate over that instead. For example:

defmodule ProtectiveEquipmentValue do
  use Ecto.Schema

  schema "protective_equipment_value" do
    field :id, :uuid
    field :name, :string
    field :age, :integer, default: 0
    has_many :posts, Post
  end

  def form_fields, do: [:name, :age]
end

Then iterate over ProtectiveEquipmentValue.form_fields instead.

I just wondered if there is maybe a special helper for this - this way I got the fields duplicated and I need to keep track of them in two places. I’ll use this solution thanks.

1 Like

Thanks for pointing this out - I saw that sometimes I have differnent values than I selected - that’s probably the reason

1 Like

Since you can make any field name map to the primary database key, or even use a composite key as the primary key in some RDBMSs, I don’t think Ecto can ever know what parts of a schema are safe to present to users as editable input. You’ll have to specify it somewhere, eventually.

That would indeed cause this problem. :slight_smile: