How to convert mix gen.live to ash compatible live view

Trying to get up to speed with Ash, and it’s really not that steep of a curve on the context and schema side of the equation. But the views and how to use ash-phoenix, is not that easy to understand and very little examples around.

I made a simple live view for a mock project im working on, a live view for a Participant in a planner like solution.

Currently I have troubles finding out how to adapt the form_component to be ash phoenix compatible.

I tried to search on google, this forum and the docs with no luck.

Maybe you y’all can help me :smiley:

this is the form_componnet Im trying to convert:

defmodule AshActivityPlannerWeb.ParticipantLive.FormComponent do
  use AshActivityPlannerWeb, :live_component

  alias AshActivityPlanner.Planner.Participant

  @impl true
  def render(assigns) do
    ~H"""
    <div>
      <.header>
        <%= @title %>
        <:subtitle>Use this form to manage participant records in your database.</:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="participant-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input field={@form[:name]} type="text" label="Name" />
        <.input field={@form[:email]} type="text" label="Email" />
        <.input field={@form[:phone]} type="text" label="Phone" />
        <.input field={@form[:description]} type="text" label="Description" />
        <:actions>
          <.button phx-disable-with="Saving...">Save Participant</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end

  @impl true
  def update(%{participant: participant} = assigns, socket) do
    changeset = Participant.change_participant(participant)

    {:ok,
     socket
     |> assign(assigns)
     |> assign_form(changeset)}
  end

  @impl true
  def handle_event("validate", %{"participant" => participant_params}, socket) do
    changeset =
      socket.assigns.participant
      |> Participant.change_participant(participant_params)
      |> Map.put(:action, :validate)

    {:noreply, assign_form(socket, changeset)}
  end

  def handle_event("save", %{"participant" => participant_params}, socket) do
    save_participant(socket, socket.assigns.action, participant_params)
  end

  defp save_participant(socket, :edit, participant_params) do
    case Participant.update_participant(socket.assigns.participant, participant_params) do
      {:ok, participant} ->
        notify_parent({:saved, participant})

        {:noreply,
         socket
         |> put_flash(:info, "Participant updated successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

  defp save_participant(socket, :new, participant_params) do
    case Participant.create_participant(participant_params) do
      {:ok, participant} ->
        notify_parent({:saved, participant})

        {:noreply,
         socket
         |> put_flash(:info, "Participant created successfully")
         |> push_patch(to: socket.assigns.patch)}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign_form(socket, changeset)}
    end
  end

  defp assign_form(socket, %Ecto.Changeset{} = changeset) do
    assign(socket, :form, to_form(changeset))
  end

  defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
end

AshActivityPlanner.Planner.Participant is a normal ash resource with the following code interface:

  code_interface do
    define_for AshActivityPlanner.Planner
    define :create
    define :read
    define :by_id, get_by: [:id], action: :read
    define :by_name, get_by: [:name], action: :read
    define :by_email, get_by: [:email], action: :read
    define :update
    define :destroy
  end

:pray:

this is actually collective learning experience so here is the full repo :slight_smile: Nothing private :wink:

I looked into

and I see its using AshPhoenix.Form.for_create and AshPhoenix.Form.for_update

        create_form: AshPhoenix.Form.for_create(Post, :create) |> to_form(),
        update_form: AshPhoenix.Form.for_update(List.first(posts, %Post{}), :update) |> to_form()

but I dont automatically know how to translate the generated “Participant.change_participant” stuff. Behind the scenes the latter would, if not using ash, be an underlying call to Participant.changeset/2

Maybe using ash will make this kind of form_component much simpler ? for ex the assign_form method might need to be removed in favour of chaining with ash phoenix.

Help is appreciated :slight_smile: And the progress will be documented. If I get it under my skin I also plan to make a guide on how to convert gen.live to ash phoenix live view :slight_smile: if no one has made it yet ofc :wink:

Your best bet is probably to use our generators mix ash_phoenix.gen.live it expects a resource and will generate a basic liveview for it.

oh … :smiley: thanks. Didn’t even know it existed. I think it should be included in the Ash phoenix guide link I linked to above :slight_smile: Because I really dont want to use normal gen.live and convert it if I can avoid it :slight_smile:
:partying_face: will try it out

It’s very new! Would love to add it to the guides.

1 Like

epic it works wonderfully :slight_smile: now I can use gen.live to make the tests :stuck_out_tongue: :smiley: (lasy af)

1 Like

before you add it to the guides, here is a bug :slight_smile:

It doesn’t add fields to the render method in show.ex

   def render(assigns) do
    ~H"""
    <.header>
      Participant <%= @participant.id %>
      <:subtitle>This is a participant record from your database.</:subtitle>

      <:actions>
        <.link patch={~p"/participants/#{@participant}/show/edit"} phx-click={JS.push_focus()}>
          <.button>Edit participant</.button>
        </.link>
      </:actions>
    </.header>

    <.back navigate={~p"/participants"}>Back to participants</.back>

    <.modal
      :if={@live_action == :edit}
      id="participant-modal"
      show
      on_cancel={JS.patch(~p"/participants/#{@participant}")}
    >
      <.live_component
        module={AshActivityPlannerWeb.ParticipantLive.FormComponent}
        id={@participant.id}
        title={@page_title}
        action={@live_action}
        participant={@participant}
        patch={~p"/participants/#{@participant}"}
      />
    </.modal>
    """
  end

they are properly present as form fields in form_components and in the index.

easy fix when I saw it however, just add the fields and tests passes :slight_smile:

EDIT: im going to check my versions

If updating doesn’t solve it please open an issue or PR in the ash_phoenix repo :bowing_man:

1 Like
      {:ash, "~> 2.17"},
      {:ash_postgres, "~> 1.3"},
      {:ash_phoenix, "~> 1.2"},

making an issue :slight_smile:

EDIT: done in mix ash_phoenix.gen.live doesn't add fields to show.ex · Issue #117 · ash-project/ash_phoenix · GitHub

1 Like