Terence Ponce
Terence Ponce

Reputation: 9381

How to use add_error on a nested changeset?

From what I understand in the documentation for add_error, It can only apply to the top-level changeset.

How do I use add_error on a nested changeset though?

For example, take these schemas:

defmodule MyApp.Person do
  use Ecto.Schema
  import Ecto.Changeset

  schema "people" do
    field :first_name, :string
    field :last_name, :string
    field :other_field_made_to_fail, :string

    has_many :addresses, MyApp.Address
  end

  def changeset(person, attrs) do
    person
    |> cast(attrs, [:first_name, :last_name, :other_field_made_to_fail])
    |> validate_required([:first_name, :last_name])
  end
end

defmodule MyApp.Address do
  use Ecto.Schema
  import Ecto.Changeset

  schema "addresses" do
    field :street_name, :string
    field :city, :string
    field :state, :string

    belongs_to :person, MyApp.Person
  end

  def changeset(address, attrs) do
    address
    |> cast(attrs, [:street_name, :city, :state])
    |> validate_required([:city, :state])
    |> cast_assoc(:person, with: &MyApp.Person.changeset/2, required: true)
  end
end

And take this data input:

%{
  "address" => %{
    "city" => "Test City",
    "state" => "Test State",
    "person" => %{
      "first_name" => "John",
      "last_name" => "Doe"
    }
  }
}

And this resulting changeset:

#Ecto.Changeset<
  changes: %{
    address: #Ecto.Changeset<
      changes: %{
        city: "Test City",
        state: "Test State"
        person: #Ecto.Changeset<
          changes: %{
            first_name: "John",
            last_name: "Doe"
          }
        >
      },
      errors: []
      valid?: true
    >
  }
>

How would I use add_error/4 to add an error to :other_field_made_to_fail inside the nested :person attribute and keep the rest of the changeset intact?

Upvotes: 1

Views: 938

Answers (1)

Terence Ponce
Terence Ponce

Reputation: 9381

I managed to figure it out. In case there are other people who are interested, the solution is to call update_change/3 on your changeset and call a function as the 3rd argument which represents your nested changeset:

Ecto.Changeset.update_change(changeset, :person, fn person_changeset ->
  Ecto.Changeset.add_error(person_changeset, :other_field_made_to_fail, "error message here")
end)

The end result is an updated Address changeset that now includes the Person changeset with an error on it.

You can also use update_change/3 to do other changeset functions into your nested changesets and it will return the updated nested changeset.

Upvotes: 3

Related Questions