Search code examples
elixirphoenix-frameworkecto

Custom validation leads to an insert/4


I don't understand why I get this error:

Abc.Maps.Location.create_location(%{name: "USA", is_country: true})
** (FunctionClauseError) no function clause matching in Ecto.Repo.Schema.insert/4 

I understand that there is no insert/4 but I don't understand why validate_presents_of_parent/1 creates the problem in the first place. What is my mistake?

I want to validate that a parent_location exists in case a location is not a country.

defmodule Abc.Maps.Location do
  use Ecto.Schema
  import Ecto.Changeset

  schema "locations" do
    field(:is_country, :boolean, default: false)
    field(:is_federal_state, :boolean, default: false)
    field(:name, :string)
    belongs_to :parent_location, Abc.Maps.Location

    timestamps()
  end

  @doc false
  def changeset(location, attrs) do
    location
    |> cast(attrs, [
      :name,
      :is_country,
      :is_federal_state,
      :parent_location_id
    ])
    |> validate_required([:name])
    |> validate_presents_of_parent()
  end

  def validate_presents_of_parent(changeset) do
    # Only a country doesn't have a parent.
    unless get_field(changeset, :is_country) do
      assoc_constraint(changeset, :parent_location)
    end
  end
end

Solution

  • It looks like you forgot to return the (unchanged) changeset from validate_presents_of_parent when :is_country is true. And you need to validate for :parent_location_id too.

      def validate_presents_of_parent(changeset) do
        # Only a country doesn't have a parent.
        if get_field(changeset, :is_country) do
          changeset
        else
          changeset
          |> validate_required([:parent_location_id])
          |> assoc_constraint(:parent_location)
        end
      end