Inputs_for not rendering

I am trying to add to a has_many association field using inputs_for.
For some reason, anything inside of the inputs_for block is not rendered.

Everything else renders just fine. I get no compile errors and logs.

Template

<h1> New Activity </h1>

<%= form_for @activity, Routes.application_event_activity_path(@conn, :create, @application_id, @event_id), fn f-> %>

<%= hidden_input f, :event_id, value: @event_id %>


<div>
    <%= label f, :name, "Name" %>
    <%= text_input f, :name %>
</div>

<%= inputs_for f, :languages, fn fp -> %>
<div>
    <%= label fp, :languages %>
    <%= select fp, :languages, Enum.map(@languages, fn x -> {x.value, x.id} end) %>
</div>
<% end %>

<%= submit "submit" %>

<% end %>

Model

defmodule DeAboriginal.Data.Activity do
  use Ecto.Schema
  import Ecto.Changeset

  alias DeAboriginal.Data.Language

  schema "activities" do
    field :name, :string
    field :event_id, :id
    has_many :languages, Language, foreign_key: :id

    timestamps()
  end

  @doc false
  def changeset(activity, attrs) do
    activity
    |> cast(attrs, [:name])
    |> cast_assoc(:languages, with: &Language.changeset/2)
    |> validate_required([:name])
  end
end

When you say, it is not rendered, do you mean you can’t see it in the browser or do you mean it’s not visible in the generated HTML?

If it’s just not visible in the browser, that might be because you used an HTML comment inside the inputs_for.

Ah so the commenting out is just me testing things. But not visible means any text, tags, elements I put inside the inputs_for, when navigating to that page it doesn’t show.

Outside inputs_for however it works just fine.

EDIT:

Just to clarify, I tested using mix phx.server and inspected the HTML using the browser. Anything between the <%= inputs_for %> and <% end %> tags do not appear.

OK, I solved my own problem.

To use has_many the changeset you provide to the template needs to contain the child changeset within it.

The following would not work since you are only providing the parent changeset. Template wouldn’t even render the inputs_for

changeset = Data.Activity.changeset(%Data.Activity{}, %{})

This does

    changeset = Data.Activity.changeset(%Data.Activity{
      languages: [Data.Language.changeset(%Data.Language{}, %{})]
    }, %{})

Now I am not sure why the former doesn’t work since the :language is included in the schema already.

This is the full new function for the controller

  def new(conn, %{
    "application_id" => app_id,
    "event_id" => event_id
  }) do

    changeset = Data.Activity.changeset(%Data.Activity{
      languages: [Data.Language.changeset(%Data.Language{}, %{})]
    }, %{})

    languages = Data.list_language()
    render(conn, "new.html",
     # additional params
      activity: changeset,
      application_id: app_id,
      event_id: event_id,
      languages: languages
      )
  end
end

1 Like

The former sets languages to [], while the second sets it to a list with one Changeset struct in it.

Try this:

    changeset = Data.Activity.changeset(%Data.Activity{
      languages: [
        Data.Language.changeset(%Data.Language{}, %{}),
        Data.Language.changeset(%Data.Language{}, %{})
      ]
    }, %{})
2 Likes

This is exactly my issue. It seems out of place that you’d have to manually “hack” it to do this. I’m not sure if they decided not to include this feature in because Elixir is known for being really fast and it’s because it doesn’t load anything unless you tell it. So it does make a little sense knowing this that we’d have to manually load and nested relationships because of their impact on global speed at a macro level.

but then again…in my case, it’s not like that for belongs_to relationships and instead just has_many relationships.