Ecto v3.4.4 Ecto.Type behaviour View Source
Defines functions and the Ecto.Type
behaviour for implementing
custom types.
A custom type expects 4 functions to be implemented, all documented and described below. We also provide two examples of how custom types can be used in Ecto to augment existing types or providing your own types.
Note: nil
values are always bypassed and cannot be handled by
custom types.
Example
Imagine you want to store an URI struct as part of a schema in an url-shortening service. There isn't an Ecto field type to support that value at runtime, therefore a custom one is needed.
You also want to query not only by the full url, but for example by specific ports used. This is possible by putting the URI data into a map field instead of just storing the plain string representation.
from s in ShortUrl,
where: fragment("?->>? ILIKE ?", s.original_url, "port", "443")
So the custom type does need to handle the conversion from
external data to runtime data (cast/1
) as well as
transforming that runtime data into the :map
Ecto native type and
back (dump/1
and load/1
).
defmodule EctoURI do
use Ecto.Type
def type, do: :map
# Provide custom casting rules.
# Cast strings into the URI struct to be used at runtime
def cast(uri) when is_binary(uri) do
{:ok, URI.parse(uri)}
end
# Accept casting of URI structs as well
def cast(%URI{} = uri), do: {:ok, uri}
# Everything else is a failure though
def cast(_), do: :error
# When loading data from the database, as long as it's a map,
# we just put the data back into an URI struct to be stored in
# the loaded schema struct.
def load(data) when is_map(data) do
data =
for {key, val} <- data do
{String.to_existing_atom(key), val}
end
{:ok, struct!(URI, data)}
end
# When dumping data to the database, we *expect* an URI struct
# but any value could be inserted into the schema struct at runtime,
# so we need to guard against them.
def dump(%URI{} = uri), do: {:ok, Map.from_struct(uri)}
def dump(_), do: :error
end
Now we can use our new field type above in our schemas:
defmodule ShortUrl do
use Ecto.Schema
schema "posts" do
field :original_url, EctoURI
end
end
Link to this section Summary
Types
Custom types are represented by user-defined modules.
Primitive Ecto types (handled by Ecto).
An Ecto type, primitive or custom.
Functions
Checks if the given atom can be used as base type.
Casts a value to the given type.
Checks if the given atom can be used as composite type.
Dumps a value to the given type.
Dumps a value to the given type.
Gets how the type is treated inside embeds for the given format.
Dumps the value
for type
considering it will be embedded in format
.
Loads the value
for type
considering it was embedded in format
.
Checks if two terms are equal.
Loads a value with the given type.
Loads a value with the given type.
Checks if a given type matches with a primitive type that can be found in queries.
Checks if we have a primitive type.
Retrieves the underlying schema type for the given, possibly custom, type.
Callbacks
Casts the given input to the custom type.
Dumps the given term into an Ecto native type.
Dictates how the type should be treated inside embeds.
Checks if two terms are semantically equal.
Loads the given term into a custom type.
Returns the underlying schema type for the custom type.
Link to this section Types
Specs
base() :: :integer | :float | :boolean | :string | :map | :binary | :decimal | :id | :binary_id | :utc_datetime | :naive_datetime | :date | :time | :any | :utc_datetime_usec | :naive_datetime_usec | :time_usec
Specs
Specs
custom() :: module()
Custom types are represented by user-defined modules.
Specs
Primitive Ecto types (handled by Ecto).
Specs
An Ecto type, primitive or custom.
Link to this section Functions
Specs
Checks if the given atom can be used as base type.
iex> base?(:string)
true
iex> base?(:array)
false
iex> base?(Custom)
false
Specs
Casts a value to the given type.
cast/2
is used by the finder queries and changesets to cast outside values to
specific types.
Note that nil can be cast to all primitive types as data stores allow nil to be set on any column.
NaN and infinite decimals are not supported, use custom types instead.
iex> cast(:any, "whatever")
{:ok, "whatever"}
iex> cast(:any, nil)
{:ok, nil}
iex> cast(:string, nil)
{:ok, nil}
iex> cast(:integer, 1)
{:ok, 1}
iex> cast(:integer, "1")
{:ok, 1}
iex> cast(:integer, "1.0")
:error
iex> cast(:id, 1)
{:ok, 1}
iex> cast(:id, "1")
{:ok, 1}
iex> cast(:id, "1.0")
:error
iex> cast(:float, 1.0)
{:ok, 1.0}
iex> cast(:float, 1)
{:ok, 1.0}
iex> cast(:float, "1")
{:ok, 1.0}
iex> cast(:float, "1.0")
{:ok, 1.0}
iex> cast(:float, "1-foo")
:error
iex> cast(:boolean, true)
{:ok, true}
iex> cast(:boolean, false)
{:ok, false}
iex> cast(:boolean, "1")
{:ok, true}
iex> cast(:boolean, "0")
{:ok, false}
iex> cast(:boolean, "whatever")
:error
iex> cast(:string, "beef")
{:ok, "beef"}
iex> cast(:binary, "beef")
{:ok, "beef"}
iex> cast(:decimal, Decimal.new("1.0"))
{:ok, Decimal.new("1.0")}
iex> cast({:array, :integer}, [1, 2, 3])
{:ok, [1, 2, 3]}
iex> cast({:array, :integer}, ["1", "2", "3"])
{:ok, [1, 2, 3]}
iex> cast({:array, :string}, [1, 2, 3])
:error
iex> cast(:string, [1, 2, 3])
:error
Specs
Checks if the given atom can be used as composite type.
iex> composite?(:array)
true
iex> composite?(:string)
false
Specs
Dumps a value to the given type.
Opposite to casting, dumping requires the returned value to be a valid Ecto type, as it will be sent to the underlying data store.
iex> dump(:string, nil)
{:ok, nil}
iex> dump(:string, "foo")
{:ok, "foo"}
iex> dump(:integer, 1)
{:ok, 1}
iex> dump(:integer, "10")
:error
iex> dump(:binary, "foo")
{:ok, "foo"}
iex> dump(:binary, 1)
:error
iex> dump({:array, :integer}, [1, 2, 3])
{:ok, [1, 2, 3]}
iex> dump({:array, :integer}, [1, "2", 3])
:error
iex> dump({:array, :binary}, ["1", "2", "3"])
{:ok, ["1", "2", "3"]}
Specs
Dumps a value to the given type.
This function behaves the same as dump/2
, except for composite types
the given dumper
function is used.
Gets how the type is treated inside embeds for the given format.
See embed_as/1
.
Dumps the value
for type
considering it will be embedded in format
.
Examples
iex> Ecto.Type.embedded_dump(:decimal, Decimal.new("1"), :json)
{:ok, Decimal.new("1")}
Loads the value
for type
considering it was embedded in format
.
Examples
iex> Ecto.Type.embedded_load(:decimal, "1", :json)
{:ok, Decimal.new("1")}
Specs
Checks if two terms are equal.
Depending on the given type
performs a structural or semantical comparison.
Examples
iex> equal?(:integer, 1, 1)
true
iex> equal?(:decimal, Decimal.new("1"), Decimal.new("1.00"))
true
Specs
Loads a value with the given type.
iex> load(:string, nil)
{:ok, nil}
iex> load(:string, "foo")
{:ok, "foo"}
iex> load(:integer, 1)
{:ok, 1}
iex> load(:integer, "10")
:error
Specs
Loads a value with the given type.
This function behaves the same as load/2
, except for composite types
the given loader
function is used.
Specs
Checks if a given type matches with a primitive type that can be found in queries.
iex> match?(:string, :any)
true
iex> match?(:any, :string)
true
iex> match?(:string, :string)
true
iex> match?({:array, :string}, {:array, :any})
true
iex> match?(Ecto.UUID, :uuid)
true
iex> match?(Ecto.UUID, :string)
false
Specs
Checks if we have a primitive type.
iex> primitive?(:string)
true
iex> primitive?(Another)
false
iex> primitive?({:array, :string})
true
iex> primitive?({:array, Another})
true
Specs
Retrieves the underlying schema type for the given, possibly custom, type.
iex> type(:string)
:string
iex> type(Ecto.UUID)
:uuid
iex> type({:array, :string})
{:array, :string}
iex> type({:array, Ecto.UUID})
{:array, :uuid}
iex> type({:map, Ecto.UUID})
{:map, :uuid}
Link to this section Callbacks
Specs
Casts the given input to the custom type.
This callback is called on external input and can return any type,
as long as the dump/1
function is able to convert the returned
value into an Ecto native type. There are two situations where
this callback is called:
- When casting values by
Ecto.Changeset
- When passing arguments to
Ecto.Query
When returning {:error, keyword()}
, the returned keyword list
will be preserved in the changeset errors, similar to
Ecto.Changeset.add_error/4
. Passing a :message
key, will override
the default message. It is not possible to override the :type
key.
For {:array, CustomType}
or {:map, CustomType}
the returned
keyword list will be erased and the default error will be shown.
Specs
Dumps the given term into an Ecto native type.
This callback is called with any term that was stored in the struct and it needs to validate them and convert it to an Ecto native type.
Specs
embed_as(format :: atom()) :: :self | :dump
Dictates how the type should be treated inside embeds.
By default, the type is sent as itself, without calling
dumping to keep the higher level representation. But
it can be set to :dump
to it is dumped before encoded.
Specs
Checks if two terms are semantically equal.
Specs
Loads the given term into a custom type.
This callback is called when loading data from the database and
receives an Ecto native type. It can return any type, as long as
the dump/1
function is able to convert the returned value back
into an Ecto native type.
Specs
type() :: t()
Returns the underlying schema type for the custom type.
For example, if you want to provide your own date
structures, the type function should return :date
.
Note this function is not required to return Ecto primitive types, the type is only required to be known by the adapter.