defmodule Exandra.UDT do
opts_schema = [
type: [
type: :atom,
required: true,
doc: "The UDT."
],
encoded_fields: [
type: {:list, :atom},
doc: "JSON encoded fields."
],
field: [
type: :atom,
doc: false
],
schema: [
type: :atom,
doc: false
]
]
@moduledoc """
`Ecto.ParameterizedType` for **User-Defined Types** (UDTs).
## Options
#{NimbleOptions.docs(opts_schema)}
## Examples
For example, if you have defined an `email` UDT in your database, you can
use it in your schema like this:
schema "users" do
field :email, Exandra.UDT, type: :email
end
"""
use Ecto.ParameterizedType
@type t() :: map()
@opts_schema NimbleOptions.new!(opts_schema)
@impl Ecto.ParameterizedType
def type(_params), do: :udt
@impl Ecto.ParameterizedType
def init(opts) do
opts
|> __validate__()
|> Map.new()
end
@impl Ecto.ParameterizedType
def cast(data, %{type: _udt}), do: {:ok, data}
@impl Ecto.ParameterizedType
def load(data, _loader, params) do
{:ok, coerce_data(data, params, :load)}
end
@impl Ecto.ParameterizedType
def dump(data, _dumper, params) do
# Stringify all keys.
data = for {field, value} <- data || %{}, into: %{}, do: {"#{field}", value}
data = coerce_data(data, params, :dump)
{:ok, data}
end
@doc false
defp coerce_data(data, params, type) do
data = data || %{}
if fields_to_encode = params[:encoded_fields] do
for field <- fields_to_encode, into: data do
stringified_field = Atom.to_string(field)
{stringified_field, json_parse(data, stringified_field, type)}
end
else
data
end
end
@doc false
def json_parse(data, field, :dump),
do: data |> Map.get(field, %{}) |> Jason.encode!()
@doc false
def json_parse(data, field, :load),
do: data |> Map.get(field, "{}") |> Jason.decode!()
@doc false
def __validate__(opts), do: NimbleOptions.validate!(opts, @opts_schema)
end