endymion

endymion

Cleanly add custom errors for Ecto.Changeset.cast/4

Found a clean solution for adding custom error messages when using Ecto.Changeset.cast/4.

The issue presented is cast/4 does not allow :message for an opts unlike other changeset functions like validate_format/4 and other custom validator functions.

This presented a problem for us as we have a number of custom types and messages that we use to validate our inputs. This works fine when validating things like a phone number because we can simply pass our custom error message directly to validate_format/4, but for our :integer and :float values we simply rely on the base cast/4 for those types without the need for more validation. When given "foo" to input type :float the changeset error message returns "is invalid". Using Gettext and traverse_errors/2 we can fix this error message on the UI, but that ends up in needing to have branching error message logic and makes it more difficult with a dozen types to clearly see where the error message definitions exist.

Instead we have a function custom_cast_error_message(changeset, field, message) that replaces the given field error only for cast/4 validations to be our custom message

  @doc """
  Overwrites the error message for `Ecto.Changeset.cast/4` from `"is invalid"` to the
  custom supplied error message.
  """
  def custom_cast_error_message(changeset, field, message) do
    errors = Enum.map(changeset.errors, fn {error_field, {_msg, opts}} = error ->
      if field == error_field && opts[:validation] == :cast do
        {field, {message, opts}}
      else
        error
      end
    end)

    %{changeset | errors: errors}
  end

A sample usage of this looks like

message = "This is our custom error message for cast/4"

Ecto.Changeset.cast(answer, params, [:value])
|> Aw.Changeset.custom_cast_error_message(:value, message)

Posting this because I saw numerous posts about this same issues and didn’t love the solutions found.

Most Liked

cjbottaro

cjbottaro

My initial thought was to do this in translate_errors using the error metadata, but I like this better since it keeps the error messages all in one place and not spread out… :+1:

LostKobrakai

LostKobrakai

defmodule PhoneNumberType do
  use Ecto.Type

  def type, do: :string

  def cast(value) do
    case Ecto.Type.cast(:string, value) do
      {:ok, value} -> {:ok, value}
      :error -> {:error, "Our custom phone number error message here"}
    end
  end

  def dump(value), do: Ecto.Type.dump(:string, value)
  
  def load(value), do: Ecto.Type.load(:string, value)
end

defmodule UnionStruct.PhoneNumber do
  use Ecto.Schema

  embedded_schema do
    field :other_data, :any
    field :value, PhoneNumberType
  end

  def changeset(changeset, params, opts) do
    Ecto.Changeset.cast(changeset, params, [:value], opts)
    |> validate_phone_number(:value)
  end
end

You can wrap generic types like this to get more specific types. If you have many similar ones you can even abstract this using Ecto.ParameterizedType.

Where Next?

Popular in Discussions Top

marcandre
Compilation time for our project degraded from ~26 seconds to ~32 seconds (~23% slower) :disappointed_face:. Did anyone else compare com...
New
heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New
bglusman
I’m curious how others in the community deal with first class enums… especially crossing boundaries like database, code logic, and absin...
New
ashkan117
I’m wondering how do people structure their JSON Api’s with Phoenix. Using the blogs example, let’s say I have a blogs view like the foll...
New
wanton7
Our company has used virtual actor framework called Orleans that is for C#/.Net Framework. There are clones of it in Erlang GitHub - erle...
New
Crowdhailer
It is rare to use direct calls to send in elixir. The call is normally wrapped in a function which is responsible for sending the correct...
New
bartblast
I recently started a discussion about reactive patterns in Hologram, but I realized I need to step back and design the composability arch...
New
AstonJ
Posting this as a separate thread as I’m sure lots of people will be curious - what will the (or the intended) differences be and when mi...
New
chrisliaw
Hi, I’m wondering is it my thinking process or this is the norm among the Elixir developer for the use of Struct and accessor functions ...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
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
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New

We're in Beta

About us Mission Statement