Home Posts Tags Post Search Tag Search

Post 71

Ash Framework 02 - Integrating LiveView

Published on: 2025-09-11 Tags: elixir, Blog, Side Project, LiveView, Ecto, Html/CSS, Phoenix, Ash, Framework
Integrating Actions into LiveViews
        Now that we have the database and some dummy entries we can try to actually look at the database and teh Phoenix integration.
            mix phx.server
            # Then head to localhost:4000 and check it out.
        
        What you are seeing on the main page is the module in lib/tunez_web/live/artist/index_live.ex. They set us up with some dummy artist but we can replace the artists with a call to the database.
            artists = Tunez.Music.read_artists!() # Notice the ! this will jsut return the artists not a condition as well

        Viewing an Artist Profile
            Clicking on an image will take you to the profile page using TunezWeb.Artists.ShowLive, again we see that there is some default data here and we can now change the default with get_artist_by_id! (again the !)
                def handle_params(%{"id" => artist_id} =  params, _url, socket) do
                    artist = Tunez.Music.get_artist_by_id!(artist_id)
                    ...
                end
        
        Creating Artists with AshPhoenix.Form
            In a normal way we would define a changeset and then cast that changeset with the Repo.insert{} like below with ash there is an other way.
            In code, the changeset function might look something like this:
                defmodule Tunez.Music.Artist do
                    def changeset(artist, attrs) do
                        artist
                        |> cast(attrs, [:name, :biography])
                        |> validate_required([:name])
                    end
            And the context module that uses it might look like this:
                defmodule Tunez.Music do
                    def create_artist(attrs \\ %{}) do
                        %Artist{}
                        |> Artist.changeset(attrs)
                        |> Repo.insert()
                    end

            "Hello, AshPhoenix"
                AshPhoenix is a core library that can do a few things but the main purpose is forms.
                    mix igniter.install ash_phoenix

            "A Form for an Action"
                This created a form for us that we can use right away. Lets try it out. 
                    iex -S mix
                    iex(1)> form = AshPhoenix.Form.for_create(Tunez.Music.Artist, :create)
                    #AshPhoenix.Form<
                    resource: Tunez.Music.Artist,
                    action: :create,
                    type: :create,
                    params: %{},
                    source: #Ash.Changeset<
                    domain: Tunez.Music,
                    action_type: :create,
                    action: :create,
                    attributes: %{},
                    ...

                    iex(2)> AshPhoenix.Form.validate(form, %{name: "Best Band Ever"})
                    #AshPhoenix.Form<
                    resource: Tunez.Music.Artist,
                    action: :create,
                    type: :create,
                    params: %{name: "Best Band Ever"},
                    source: #Ash.Changeset<
                    domain: Tunez.Music,
                    action_type: :create,
                    action: :create,
                    attributes: %{name: "Best Band Ever"},
                    relationships: %{},
                    errors: [],
                    data: %Tunez.Music.Artist{...},
                    valid?: true➤
                    >,
                    ...

                    iex(5)> AshPhoenix.Form.submit(form, params: %{name: "Best Band Ever"})
                    INSERT INTO "artists" ("id","name","inserted_at","updated_at") VALUES
                    ($1,$2,$3,$4) RETURNING "updated_at","inserted_at","biography","name","id"
                    [[uuid], "Best Band Ever", [timestamp], [timestamp]]
                    {:ok,
                    %Tunez.Music.Artist{
                    id: [uuid],
                    name: "Best Band Ever",
                    ...

                As you can see we have some built in functions that will create and then validate as well as insert the new band into the database. With this taken care of we can now try to add the domain to the Domain so that we can now use the form to do all the above

            "Using the AshPhoenix Domain Extension"
                The functionality that we are using atm is to call the :create atom and that feels a bit problematic for what we have already done. With that being said we can now add the AshPhoenix extension to the Music Domain to leverage the new form.
                    defmodule Tunez.Music do
                        use Ash.Domain, otp_app: :tunez, extensions: [AshPhoenix]➤
                        # ...    
                
                Lets try out the new way of doing things. Now we can simply append the form_to to any function and it will create a form to do that thing. Keep in mind that you will need to have the extension within the module.
                    iex(5)> AshPhoenix.Form.for_create(Tunez.Music.Artist, :create)
                    #AshPhoenix.Form<resource: Tunez.Music.Artist, action: :create, ...>
                    iex(6)> Tunez.Music.form_to_create_artist()
                    #AshPhoenix.Form<resource: Tunez.Music.Artist, action: :create, ...>

            "Integrating a Form into a LiveView"
                We can now go to lib/tunez/live/artists/form_live.ex this is where you would go to create a new artist form.
                    form = Tunez.Music.form_to_create_artist()

                Its really that simple. So long as you have some form of the html for the render you will not have access to the form for anything that has the extension installed. There is a built in validate event that is currently not doing anything so we should now define it.
                    def handle_event("validate", %{"form" => form_data}, socket) do
                        socket =
                        update(socket, :form, fn form ->
                            AshPhoenix.Form.validate(form, form_data)
                        end)
                        {:noreply, socket}
                    end

                We now need to take care of the submit there is also a way to do that as well. Keep in mind that you are calling the "save" event when you save the form. It is already defined below the render so lets flesh it out. You will need to take care of 2 events a valid submit and an issue both of those should have some sort of flash message as well as put you back into a page.
                    def handle_event("save", %{"form" => form_data}, socket) do
                        case AshPhoenix.Form.submit(socket.assigns.form, params: form_data) do
                        {:ok, artist} ->
                            socket =
                            socket
                            |> put_flash(:info, "Artist saved successfully")
                            |> push_navigate(to: ~p"/artists/#{artist}")

                            {:noreply, socket}

                        {:error, form} ->
                            socket =
                            socket
                            |> put_flash(:error, "Could not save artist data")
                            |> assign(:form, form)

                            {:noreply, socket}
                        end
                    end

                Great now we can add an artist but we don't have a way to edit and then delete.

        Updating Artists with the same Code
            Remember that the update and create were very similar function calls where one just needs an artist id to be called as well. Also keep in mind that once you are looking at an artist you will have the artist within the params of the page. So so long as you have those values and functions you can create or edit an artist. What is great is that we can pattern match to an existing artist in the mount so that we can pull the data for the artist and then do an update.
                def mount(%{"id" => artist_id}, _session, socket) do
                    artist = Tunez.Music.get_artist_by_id!(artist_id)
                    form = Tunez.Music.form_to_update_artist(artist)

                    socket =
                    socket
                    |> assign(:form, to_form(form))
                    |> assign(:page_title, "Update Artist")

                    {:ok, socket}
                end

            Simple but keep in mind that you are setting the form to have the existing data for the artist then you can edit with an update.
                

        Deleting Artists Data
            This is the destroy action that we created earlier. We now need to head to the show_live.ex in order to create this event. Looking at the show_live.ex we see that the delete artist button will call the delete event. So in order to do this we need to pass the artists id and then run the destroy function to do so.
                def handle_event("destroy-artist", _params, socket) do
                    case Tunez.Music.destroy_artist(socket.assigns.artist) do
                    :ok ->
                        socket =
                        socket
                        |> put_flash(:info, "Artist deleted successfully")
                        |> push_navigate(to: ~p"/")

                        {:noreply, socket}

                    {:error, error} ->
                        Logger.info("Could not delete artist '#{socket.assigns.artist.id}':#{inspect(error)}")

                        socket =
                        socket
                        |> put_flash(:error, "Could not delete artist")

                        {:noreply, socket}
                    end
                end

    That is it we now have forms for creating an artist editing an artists and even the funcionality for deleting an artist. There is so much that you can do here to make this easier but we will more on to more functionality.