Ecto.Type

Parsed documentation:
View on GitHub
Defines functions and the `Ecto.Type` behaviour for implementing
custom types.

A custom type expects 5 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.

## Augmenting types

Imagine you want to support your id field to be looked up as a
permalink. For example, you want the following query to work:

    permalink = "10-how-to-be-productive-with-elixir"
    from p in Post, where: p.id == ^permalink

If `id` is an integer field, Ecto will fail in the query above
because it cannot cast the string to an integer. By using a
custom type, we can provide special casting behaviour while
still keeping the underlying Ecto type the same:

    defmodule Permalink do
      def type, do: :integer

      # Provide our own casting rules.
      def cast(string) when is_binary(string) do
        case Integer.parse(string) do
          {int, _} -> {:ok, int}
          :error   -> :error
        end
      end

      # We should still accept integers
      def cast(integer) when is_integer(integer), do: {:ok, integer}

      # Everything else is a failure though
      def cast(_), do: :error

      # Integers are never considered blank
      def blank?(_), do: false

      # When loading data from the database, we are guaranteed to
      # receive an integer (as database are stricts) and we will
      # just return it to be stored in the model struct.
      def load(integer) when is_integer(integer), do: {:ok, integer}

      # When dumping data to the database, we *expect* an integer
      # but any value could be inserted into the struct, so we need
      # guard against them.
      def dump(integer) when is_integer(integer), do: {:ok, integer}
      def dump(_), do: :error
    end

Now, we can use our new field above as our primary key type in models:

    defmodule Post do
      use Ecto.Model

      @primary_key {:id, Permalink, []}
      schema "posts" do
        ...
      end
    end

## New types

In the previous example, we say we were augmenting an existing type
because we were keeping the underlying representation the same, the
value stored in the struct and the database was always an integer.

However, sometimes, we want to completely replace Ecto data types
stored in the models. For example, data intensive applications may
find the `%Ecto.Datetime{}` struct, used by `:datetime` columns, too
simple and wish to use a more robust alternative.

This can be achieved by implementing the proper `load/1` and `dump/1`
functions that cast the database types into another struct:

    defmodule SuperDateTime do
      defstruct [:year, :month, :day, :hour, :min, :sec]

      def type, do: :datetime

      # Provide our own casting rules.
      def cast(string) when is_binary(string) do
        # Here, for example, you could try to parse different string formats.
      end

      # Our custom datetime should also be valid
      def cast(%SuperDateTime{} = datetime) do
        {:ok, datetime}
      end

      # Everything else needs to be a failure though
      def cast(_), do: :error

      # Datetimes are never considered blank
      def blank?(_), do: false

      # When loading data from the database, we need to
      # convert the Ecto type to our type:
      def load({{year, month, day}, {hour, min, sec}}) do
        {:ok, %SuperDateTime{year: year, month: month, day: day,
                             hour: hour, min: min, sec: sec}}
      end

      # When dumping data to the database, we need to convert
      # our type back to Ecto.DateTime one:
      def dump(%SuperDateTime{} = dt) do
        {:ok, {{dt.year, dt.month, dt.day}, {dt.hour, dt.min, dt.sec}}}
      end
      def dump(_), do: :error
    end

Now we can use in our fields too:

    field :published_at, SuperDateTime

And that is all. By defining a custom type, we were able to extend Ecto's
casting abilities and also any Elixir value in our models while preserving
Ecto guarantees to safety and type conversion.
No suggestions.
Please help! Open an issue on GitHub if this assessment is incorrect.