Polymorphic_embed for LiveView 1.1

Does anyone have a working example of polymorphic_embed working with LiveView 1.1? Specifically the live view part (the backend part seems to work just fine).

I have followed the documentation but I see nothing on the browser. Nothing is emitted on the html if I inspect it.

Looking at the github issues/pull requests, it seems like it is kinda abandoned for the last year.

Any pointers to a working example or a fork that works for current (1.1) live view?

Thanks!

I’ve been using it, but in a project that’s still in development. Its working great for me. Do you have any code to post, like the Schema and LiveView that use polymorphic_embed?

I do remember being confused about the usage, but it working.

it’s very simple a client has polymorphic name depending on its type (individual or legal entity):

 defmodule MyApp.Client do

  schema "clients" do
    field :client_type, Ecto.Enum, values: [:legal_entity, :individual]

    polymorphic_embeds_one(:client_name,
      types: [
        legal_entity: LegalEntityName,
        individual: IndividualName
      ],
      on_replace: :update
    )
  end
end

defmodule MyApp.LegalEntityName do
  embedded_schema do
    field :corporate_name, :string
    field :trade_name, :string
  end
end

defmodule MyApp.IndividualName do

  embedded_schema do
    field :name, :string
    field :first_surname, :string
    field :second_surname, :string
  end
end

defmodule MyAppWeb.ClientLive.Form do
  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_scope={@current_scope}>
      <.form for={@form} id="client-form" phx-change="validate" phx-submit="save">
        <.input
          field={@form[:client_type]}
          type="select"
          label={gettext("Client type")}
          prompt={gettext("Choose a value")}
          options={ClientType.options()}
        />
        <.polymorphic_embed_inputs_for :let={name} field={@form[:client_name]}>
          <.input field={name[:corporate_name]} type="text" label={gettext("Corporate name")} />
          <%= case source_module(name) do %>
            <% LegalEntityName -> %>
              <.input field={name[:trade_name]} type="text" label={gettext("Trade name")} />
            <% IndividualName -> %>
              <.input field={name[:name]} type="text" label={gettext("Name")} />
              <.input field={name[:first_surname]} type="text" label={gettext("First surname")} />
          <% end %>
        </.polymorphic_embed_inputs_for>
      </.form>
    </Layouts.app>
    """
  end

  defp apply_action(socket, :new, _params) do
    client = %Client{user_id: socket.assigns.current_scope.user.id}

    socket
    |> assign(:client, client)
    |> assign(:form, to_form(Clients.change_client(socket.assigns.current_scope, client)))
  end
end

When the page is rendered, the `client_type` dropdown and the form renders just fine, but the polymorphic_embed does not. The html doesn’t even contain anything other than the heex debug comments, but nothing in between.

<!-- @caller lib/myapp_web/live/client_live/form.ex:36  -->
<!-- @caller lib/myapp_web/live/client_live/form.ex:46  -->

I have tried a lot of variations:

  • setting `type_field_name`
  • using `use_parent_field_for_type` to point to the `client_type` dropdown
  • setting ` %Client{user_id: socket.assigns.current_scope.user.id, client_type: :individual}` on the `apply_action`.

In summary, I don’t know if I’m using this library the way it is supposed to be used. What I want is:

  • on new form, show empty fields for the type selected in the `client_type` dropdown (or a default generated when creating the struct passed to `to_form`
  • on edit, render the fields with the data fetched from the Repo and loaded into the struct.
  • dinamically change the fields shown depending on the client_type.

Nothing too complicated in my opinion.

Am I using this library correctly?

Thanks

Ok, I think I got it to work:

I added a default to the client_type:

    field :client_type, Ecto.Enum, values: [:legal_entity, :individual], default: :legal_entity

Then the new puts the correct embedded struct, depending on the `client_type`:



  defp apply_action(socket, :new, _params) do
    client = %Client{user_id: socket.assigns.current_scope.user.id}

    client =
      case client.client_type do
        :legal_entity ->
          %Client{client | client_name: %LegalEntityName{}}

        :individual ->
          %Client{client | client_name: %IndividualName{}}
      end

    socket
    |> assign(:page_title, gettext("New client"))
    |> assign(:client, client)
    |> assign(:form, to_form(Clients.change_client(socket.assigns.current_scope, client)))
  end



And when validating, if the `client_type` changed, I change the polymorphic_embed struct too:


  @impl true
  def handle_event("validate", %{"client" => client_params}, socket) do
    changeset =
      Clients.change_client(socket.assigns.current_scope, socket.assigns.client, client_params)

    changeset =
      case Ecto.Changeset.get_change(changeset, :client_type) do
        nil -> changeset
        :legal_entity -> Ecto.Changeset.put_change(changeset, :client_name, %LegalEntityName{})
        :individual -> Ecto.Changeset.put_change(changeset, :client_name, %IndividualName{})
      end

    {:noreply, assign(socket, form: to_form(changeset, action: :validate))}
  end

The problem with replacing the client_name on the changeset when `client_type` changes is that the previous values are lost, but is something I can live with.

Thanks!

1 Like

Cool! Sorry for the late response, but I’m glad you figure it out. Like I said, I also remember running into some walls getting it to work the way I though it was supposed to. Like you, I eventually got it working and it has been great for me ever since. Best regards!

3 Likes