How to display inputs_for only if the has_one assciation exists or is about to be created

I’m trying to build a form where some has_one relations are optionnal.

Like in the case of has_many relations I want to display inputs_for fields only if the association already exists or if the user clic on a button to add the fields with some javascript.

Unfortunately when I use has_one relationship the fields are always present and I can’t hide them properly.

Is there a simple way to make them work as if the relation where defined as has_many?

Here is some code to illustrate the situation:

schema "people" do
  ....
  has_one :phone_number, MyApp.People.PhoneNumber
  ...
end


def changeset(person, attrs) do
  person
  ....
  |> cast_assoc(:phone_number)
  ....
end

schema "phone_numbers" do
  belongs_to :person, MyApp.People.Person
  field :number, :integer
end


def new(conn, _params) do 
  ...
  changeset = People.Person.change_person(%Person{})
  ...
end


<%= form_for @changeset, @action, fn f -> %>
  ...... person fields .....

    <%= inputs_for f, :phone_number, fn pn -> %>
       <%= numner_input pn, :phone_number %>
    <% end %>

<% end %>

If I convert the has_one to has_many it works but I feel like I’m not doing the right thing and I have to change a lot of thing elsewhere to reflect this “fake” has_many relationship.

I’ve try to encapsulate the inputs_for with things like:

if/unless @changeset.data.phone_number do ....
if/unless f.data.phone_number do ....

But this is not working properly.

What explains the difference of behavior of inputs_for with has_many and has_one ?
Any idea how to do it ?

The only work around for now is:

<%= if (Map.has_key?(@changeset.data, :phone_number) and f.params["phone_number"] != nil) or f.data.phone_number do %>
  <%= inputs_for f, :phone_number, fn pn -> %>
     <%= numner_input pn, :phone_number %>
  <% end %>
<% end %>

But I think I’m missing something.

The next ecto release will come with Ecto.Changeset.get_assoc(cs, :phone_number) which will give you a clean Ecto.Changeset.t | nil return value for a has_one relationship, which you can switch rendering on for any case. Until then you’d need to do Ecto.Changeset.get_change(cs, :phone_number) || Ecto.Changeset.get_field(cs, :phone_number)

Edit: Someone had linked to this from slack and I though this was a new thread not an old one. Sorry for the necro.

1 Like

@LostKobrakai I wasn’t aware of the new get_assoc so I’m glad you dug up the question!