silviurosu

silviurosu

Phoenix Liveview form does not show errors on associated objects

I struggle to figure out an issue with a form validation.
I have a phoenix liveview form that containts a nested model. I render the form like this:

 <.simple_form for={@form} id="specialist_sign_up_job_form" phx-submit="submit_fields" phx-change="update">
....
<.inputs_for :let={adr} field={@form[:address]}>
      <.input field={adr[:county]} type="select" id="address-county" label={gettext("Judet")} options={@counties} prompt={gettext("Alegeti judetul")} phx-change="load_cities" />
      <.input field={adr[:city]} type="select" id="address-city" label={gettext("Oras")} options={@cities} prompt={gettext("Alegeti orasul")} />
       <.input field={adr[:street]} type="text" id="address-street" parent_class="md:col-span-2" class="border border-brd text-gray-500 text-sm rounded-xl focus:ring-primary-600 focus:border-primary-600 block w-full px-3 py-4" label={gettext("Adresa")} phx-debounce="400" />

     <.input id="address-id" field={adr[:id]} type="hidden" />
     <.input id="address-country" field={adr[:country]} type="hidden" value={gettext("Romania")} />
     <.input id="address-lat" field={adr[:lat]} type="hidden" />
     <.input id="address-lng" field={adr[:lng]} type="hidden" />
 </.inputs_for> 
<./simple_form>

My model

defmodule Recenza.Models.Specialist do
schema "specialists" do
   .......
    belongs_to(:address, Address, foreign_key: :address_id)
end

def job_data_changeset(specialist, attrs, validate_required \\ true) do
    ch =
      specialist
      |> cast(attrs, @job_allowed_fields)
      |> maybe_validate_required_company(specialist)

    if validate_required do
      ch
      |> cast_assoc(:address, required: true)
      |> put_assoc(:speciality, parse_speciality(attrs), required: true)
      |> validate_required(@job_required_fields)
      |> geolocate_address()
    else
      ch
      |> cast_assoc(:address)
      |> put_assoc(:speciality, parse_speciality(attrs))
    end
  end
end

and the association model

defmodule Recenza.Models.Address do
......
def changeset(address, attrs) do
    address
    |> cast(attrs, @allowed_fields)
    |> validate_required(@required_fields)
  end
end

In PhenixLiveview process I am have this code:

def handle_event("submit_fields", %{"specialist" => specialist_params}, socket) do
    user = socket.assigns.current_user
    specialist = user.specialist

    changeset = Specialist.job_data_changeset(specialist, specialist_params, true)

    case Repo.insert_or_update(changeset) do
      {:ok, specialist} ->
        socket = assign(socket, :current_user, %User{user | specialist: specialist})

        {:noreply,
         socket
         |> put_flash(:info, "specialist job data data saved")
         |> push_navigate(to: ~p"/specialist_sign_up_services")}

      {:error, changeset} ->
        {:noreply, assign(socket, :form, to_form(changeset, as: :specialist))}
    end
  end

I am printing in console the form after each call to server side and the form attribute looks like this:

UDATED CHANGESET: #Ecto.Changeset<
  action: :update,
  changes: %{
    address: #Ecto.Changeset<
      action: :insert,
      changes: %{country: "România"},
      errors: [
        street: {"can't be blank", [validation: :required]},
        city: {"can't be blank", [validation: :required]},
        county: {"can't be blank", [validation: :required]}
      ],
      data: #Recenza.Models.Address<>,
      valid?: false
    >,
    employment_status: "employee",
    speciality: #Ecto.Changeset<
      action: :insert,
      changes: %{},
      errors: [
        name: {"can't be blank", [validation: :required]},
        category_id: {"can't be blank", [validation: :required]}
      ],
      data: #Recenza.Models.Speciality<>,
      valid?: false
    >
  },
  errors: [
    description: {"can't be blank", [validation: :required]},
    company_name: {"Compania este necesara pentru angajati", []}
  ],
  data: #Recenza.Models.Specialist<>,
  valid?: false
>

As you can see there are validation errors on base model and also on the association.
The problem is that the UI does not render the errors on the address, only on specialist:

This happens every time I complete any of the field from specialist. If I do not complete any field and just try to submit the form as is I can see the errors on the address too:

I spent a lot of time trying it figure this out but I could now. Can you guys give me any hint on what I am missing??
I guess it’s something related to fact that the form does not see the nested object as changed but I am stuck at this point

Most Liked

sodapopcan

sodapopcan

I gave this a quick look. The main thing that sticks out is:

<.input id="address-id" field={adr[:id]} type="hidden" />

<.inputs_for /> takes care of this. I’m not sure what kind of effect that would have have but you definitely shouldn’t include it.

Last Post!

silviurosu

silviurosu

I think I found the reason for the missing errors on UI.
The model I am starting with is this one:

%Recenza.Models.Specialist{
  __meta__: #Ecto.Schema.Metadata<:loaded, "specialists">,
  id: 502,
  description: nil,
  title: nil,
  facebook: nil,
  twitter: nil,
  instagram: nil,
  website: nil,
  phone: "0757220577",
  registration_date: nil,
  active: false,
  company_name: nil,
  employment_status: nil,
  rating: nil,
  reviews_count: nil,
  user_id: 1002,
  user: #Ecto.Association.NotLoaded<association :user is not loaded>,
  address_id: nil,
  address: nil,
  speciality_id: nil,
  speciality: nil,
  gallery_id: nil,
  gallery: #Ecto.Association.NotLoaded<association :gallery is not loaded>,
  reviews: #Ecto.Association.NotLoaded<association :reviews is not loaded>,
  services: [],
  inserted_at: ~N[2024-01-06 14:25:58],
  updated_at: ~N[2024-01-06 14:25:58]
}

As you can see the address association is empty at the moment.
The first change in the form calls “update” with these parameters:

%{
  "address" => %{
    "_persistent_id" => "0",
    "city" => "",
    "country" => "România",
    "county" => "",
    "lat" => "",
    "lng" => "",
    "street" => ""
  },
  "company_name" => "",
  "description" => "",
  "employment_status" => "employee",
  "facebook" => "",
  "instagram" => "",
  "phone" => "0757220577",
  "speciality" => %{
    "_persistent_id" => "0",
    "category_id" => "",
    "id" => "",
    "name" => "",
    "name_text_input" => ""
  },
  "speciality_id" => "",
  "user_id" => "1002",
  "website" => ""
}

nested address comes as a new empty map.
which transforms the Specialist changeset into this:

AFTER ASSIGN: #Ecto.Changeset<
  action: nil,
  changes: %{
    address: #Ecto.Changeset<
      action: :insert,
      changes: %{country: "România"},
      errors: [
        street: {"can't be blank", [validation: :required]},
        city: {"can't be blank", [validation: :required]},
        county: {"can't be blank", [validation: :required]}
      ],
      data: #Recenza.Models.Address<>,
      valid?: false
    >,
    employment_status: "employee",
    speciality: #Ecto.Changeset<
      action: :insert,
      changes: %{},
      errors: [
        name: {"can't be blank", [validation: :required]},
        category_id: {"can't be blank", [validation: :required]}
      ],
      data: #Recenza.Models.Speciality<>,
      valid?: false
    >
  },
  errors: [
    description: {"can't be blank", [validation: :required]},
    speciality_id: {"can't be blank", [validation: :required]},
    company_name: {"Compania este necesara pentru angajati", []}
  ],
  data: #Recenza.Models.Specialist<>,
  valid?: false
>

Address related model has been added in the changeset with the action :insert

If I submit the form now and print in the logs the changeset before submit and after Repo.insert I can see this:

CHANGESET BEFORE SAFE:

 #Ecto.Changeset<
  action: nil,
  changes: %{
    address: #Ecto.Changeset<
      action: :insert,
      changes: %{country: "România"},
      errors: [
        street: {"can't be blank", [validation: :required]},
        city: {"can't be blank", [validation: :required]},
        county: {"can't be blank", [validation: :required]}
      ],
      data: #Recenza.Models.Address<>,
      valid?: false
    >,
    employment_status: "employee",
    speciality: #Ecto.Changeset<
      action: :insert,
      changes: %{},
      errors: [
        name: {"can't be blank", [validation: :required]},
        category_id: {"can't be blank", [validation: :required]}
      ],
      data: #Recenza.Models.Speciality<>,
      valid?: false
    >
  },
  errors: [
    description: {"can't be blank", [validation: :required]},
    speciality_id: {"can't be blank", [validation: :required]},
    company_name: {"Compania este necesara pentru angajati", []}
  ],
  data: #Recenza.Models.Specialist<>,
  valid?: false
>

CHANGESET AFTER SAVE:

 #Ecto.Changeset<
  action: :update,
  changes: %{
    address: #Ecto.Changeset<
      action: :insert,
      changes: %{country: "România"},
      errors: [
        street: {"can't be blank", [validation: :required]},
        city: {"can't be blank", [validation: :required]},
        county: {"can't be blank", [validation: :required]}
      ],
      data: #Recenza.Models.Address<>,
      valid?: false
    >,
    employment_status: "employee",
    speciality: #Ecto.Changeset<
      action: :insert,
      changes: %{},
      errors: [
        name: {"can't be blank", [validation: :required]},
        category_id: {"can't be blank", [validation: :required]}
      ],
      data: #Recenza.Models.Speciality<>,
      valid?: false
    >
  },
  errors: [
    description: {"can't be blank", [validation: :required]},
    speciality_id: {"can't be blank", [validation: :required]},
    company_name: {"Compania este necesara pentru angajati", []}
  ],
  data: #Recenza.Models.Specialist<>,
  valid?: false
>

between those two changesets, for Specialist action has changed from nil to :update but for Address it’s the same :insert vs insert
For Phoenix nothing changed in the address fields to show errors, I guess.

The hack I found it works for now it’s like this. When computing the changeset for the initial form instead of this:

def job_data_changeset(specialist, attrs) when attrs == %{} do
    change(specialist)
  end

I have change it to this:

def job_data_changeset(specialist, attrs) when attrs == %{} do
    specialist =
      if is_nil(specialist.address) do
        Map.put(specialist, :address, %Address{})
      else
        specialist
      end

    change(specialist)
  end

It seems that for nested association I have to create an empty model when initializing the form.

Where Next?

Popular in Questions Top

jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 44778 311
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42716 114
New

We're in Beta

About us Mission Statement