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
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… ![]()
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.
Last Post!
LostKobrakai
defmodule CustomString do
use Ecto.ParameterizedType
def init(opts) do
%{error: opts[:validation_message]}
end
def type(_), do: :string
def cast(value, params) do
case Ecto.Type.cast(:string, value) do
{:ok, value} -> {:ok, value}
:error -> {:error, params.error}
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 :value, CustomString, validation_message: "abc"
end
def changeset(changeset, params, opts) do
Ecto.Changeset.cast(changeset, params, [:value], opts)
|> validate_phone_number(:value)
end
end
Popular in Discussions
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









