Using Ecto (without Db) for validating Phoenix form
Tags: ecto, liveview, phoenix
Published: 2024-06-08 07:51:53

I had a sharing session for Elixir community in Saigon about how to using Ecto without db for validating Phoenix form. Now I add this for people who just start to learn Elixir can see what can do with Ecto & Phoenix form.

For save time I just write an example for HTML form in LiveView.

As we known Ecto is very flexible library for Elixir application and people usually use Ecto like:

![Ecto common case](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88jgyp1wu3fsk9h89jci.png)

Actually, we can use only 2 modules are enough for casting & validating form: Ecto Changeset & Schema (you can use only Changeset module but with Schema much more convenience).

For example:
I declare Schema for Candidate module:
```Elixir
defmodule DemoEctoForm.Candidate do
  use Ecto.Schema
  import Ecto.Changeset

  embedded_schema do
    field :name, :string
    field :bio, :string
  end

  def changeset(candidate, attrs \\ %{}) do
    candidate
    |> cast(attrs, [:name, :bio])
    |> validate_required([:name])
    |> update_change(:name, &String.trim/1)
    |> validate_format(:name, ~r/^[a-zA-Z\s]+$/)
    |> validate_format(:name, ~r/^\w+(?:\s+\w+){1,5}$/)
    |> validate_format(:bio, ~r/^\w+(?:\s+\w+){2,25}$/)
  end
end

```
A embedded schema with only 2 fields & a changeset/2 function for casting & validating values from submitted form.

For validating use can see one required field (`:name`) and I add some regex for checking valid fields.

And at LiveView I defined a template:
```Elixir
    ~H"""
    <div class="bg-red-600 text-white rounded-md">
    <br>
    <.form
      id="candidate-form"
      :let={f} for={@changeset}
      phx-change="validate"
      phx-submit="submit"
      class="flex flex-col max-w-md mx-auto mt-8"
    >
     <h1 class="text-4xl font-bold text-center">New Candidate!</h1>
     <br>
      <.input field={f[:name]} placeholder="Nguyen Van Great" id="name" label="Full Name" />
      <br>
      <.input field={f[:bio]} placeholder="example: Elixir, Phoenix, Ecto, Hòzô" id="bio" label="Bio"/>
      <br>
      <.button type="submit">Add Candidate</.button>
      <br><br>
    </.form>
    </div>
    """
```
I passed default changeset from mount event then use directly in form. See `.form` and `.input`

My mount event:
```Elixir
  def mount(_params, _session, socket) do
    changeset = Candidate.changeset(%Candidate{})

    {:ok, assign(socket, changeset: changeset)}
  end
```
And add `phx-submit` event to handle submitted form from user:
```Elixir

  def handle_event("submit", %{"candidate" => candidate_params}, socket) do
    changeset =
      %Candidate{}
      |> Candidate.changeset(candidate_params)

    if changeset.valid? do
      data = apply_action!(changeset, :update)
      IO.puts "Candidate data: #{inspect(data, [pretty: true, struct: false])}"
      Ets.add_candidate(data)

      socket =
        socket
        |> put_flash(:info, "You added a candidate!")
        |> redirect(to: ~p"/")

      {:noreply, socket}
    else
      changeset =
        %Candidate{}
        |> Candidate.changeset(candidate_params)

      {:noreply, assign(socket, changeset: changeset)}
    end
  end
```
I cast data from submitted form to changeset then check if changeset is valid or not. If changeset is invalid I pass again socket for form show errors in browser then user can update data.

Now, I have a completed form!

Actually, I have added an other tips for user can continue fill to the form in another device/browser or in case if LiveView is crashed by save state of form to `ets` table in my project.

Check [my repo](https://github.com/ohhi-vn/sharing_ecto_form) for more.