silviurosu

silviurosu

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

Showing Posts 1 to 9

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.

D4no0

D4no0

Is there a reason you are using adr[:county] instead of adr.country? This makes it pretty hard to track down potential missing map keys.

silviurosu

silviurosu OP

I tried to replace add.county like you said and I get this error:

 (KeyError) key :county not found in: %Phoenix.HTML.Form{source: #Ecto.Changeset<action: nil, changes: %{}, errors: [], data: #Recenza.Models.Address<>, valid?: true>, impl: Phoenix.HTML.FormData.Ecto.Changeset, id: "specialist_address_0", name: "specialist[address]", data: %Recenza.Models.Address{__meta__: #Ecto.Schema.Metadata<:built, "addresses">, id: nil, street: nil, city: nil, zip: nil, county: nil, country: nil, lat: nil, lng: nil, specialist: #Ecto.Association.NotLoaded<association :specialist is not loaded>, inserted_at: nil, updated_at: nil}, hidden: [{"_persistent_id", "0"}], params: %{"_persistent_id" => "0"}, errors: [], options: [], index: 0, action: nil}

I used the same format as in the documentation.

silviurosu

silviurosu OP

I removed that hidden input for address[:id] but it does not make any difference

nseaSeb

nseaSeb

Hello, do you have the required fields in @required_fields ?

|> validate_required(@required_fields)

sodapopcan

sodapopcan

Interesting, ya I’m not sure. I notice you also have a duplicate adr[:country] going on. You also don’t need to specify an field ids manually as that is also taken care of you. That should really not make a difference but just grasping at straws here. Might need to see more code. Does it have anything to do with that validate_required check (the variable one, not the function)? In one path you’re saying address is required and in the other you are not.

silviurosu

silviurosu OP

I have added that param validate_required because it was validating the form even the first time it was displaying and I do not want that.
I am not sure what other code to share, I guess I shared whatever is relevant. I’ll try to debug it more

sodapopcan

sodapopcan

The changeset will be shown as “invalid” but that’s ok, the errors won’t show in the UI on initial render. You definitely don’t need this.

silviurosu

silviurosu OP

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.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New
webofbits
Aludel - LLM Evaluation Workbench Aludel is an embeddable Phoenix LiveView dashboard for evaluating and comparing LLM prompts across mult...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews