mathieuprog / polymorphic_embed

Polymorphic embeds in Ecto
Apache License 2.0
340 stars 62 forks source link

How is one supposed to display the polymorphic embed fields in a form if the schema is empty? #73

Open maxmarcon opened 2 years ago

maxmarcon commented 2 years ago

First of all, thanks a ton for this great library 👍

I have the following schema (abridged):

defmodule ClientApiConfig do 

 schema "client_api_config" do

 polymorphic_embeds_one(:args,
      types: [
        legal_tracker: DataIngestion.LegalTracker.ApiArgs,
        counsellink: DataIngestion.Counsellink.ApiArgs,
        ebiller_mock: DataIngestion.Api.DummyArgs
      ],
      on_replace: :update,
      on_type_not_found: :changeset_error
    )
 end
end

Then in my HTML form, following the LiveView example I have:

<%= for args_form <- polymorphic_embed_inputs_for(f, :args) do %>
        <%= hidden_inputs_for(args_form) %>
        <%= case get_polymorphic_type(f, ClientApiConfig, :args) do %>

       <% :counsellink -> %>
           ... inputs for counsellink

      <% :legal_tracker -> %>
           ...inputs for legal_tracker 

      <% end %>
<% end %>

(sidenote, I think the LiveView example is wrong, it should be get_polymorphic_type(f, Reminder, :channel) and not get_polymorphic_type(channel_form, Reminder, :channel))

The above form works if I'm editing new data which already contains an :args embed. If however I want to edit a new record and I start from an empty %ClientApiConfig{}, no fields will be rendered because polymorphic_embed_inputs_for(f, :args) returns [] (there's no :args embed and therefore no :__type__ field in it, I guess).

How can I enforce a default type? I can do that with polymorphic_embed_inputs_for/4, but since I'm using LV, I thought I'm supposed to use polymorphic_embed_inputs_for/2. Or perhaps I should use the undocumented to_form/5?

Seems to me that polymorphic_embed_inputs_for/2 should be extended to accept a type parameter.

woylie commented 1 year ago

You should be able to initialize the changeset with a default type like this: %ClientApiConfig{args: %DataIngestion.LegalTracker.ApiArgs{}}

A polymorphic_embed_inputs_for(form, field, type) would be nice, but there is already another 3-arity variant. Although the variants that take a function as argument could be deprecated in my mind, since everything is moving towards Phoenix components and away from the old function-based HTML helpers.

maxmarcon commented 1 year ago

You should be able to initialize the changeset with a default type like this: %ClientApiConfig{args: %DataIngestion.LegalTracker.ApiArgs{}}

True, but in my application (I didn't mention this to keep the example simple), when creating a new record, the user is supposed to select the polymorphic type from a dropdown. This means that the selection of the type happens after the changeset is initialized. I could of course react to changes in the dropdown value and rebuild a changeset with the right type for args, but in the end I found it simpler to just use to_form:

<%= for args_form <- to_form(f.source, f, :args, input_value(form, :dropdown), []) do %>

<% end %>

Not very elegant probably, but it gets the job done. Glad you kept to_form public 😃 ! As you said, a polymorphic_embed_inputs_for(form, field, type) or equivalent function would be nice.

samrat commented 8 months ago

Is there a full example of how to do this somewhere?

I've also run into the same problem and have gotten around it by adding an extra type field to my schema that is set using a dropdown. Is there a better way?