defmodule Exandra.Set do
opts_schema = [
type: [
type: :atom,
required: true,
doc: "The type of the elements in the set."
],
field: [
type: :atom,
doc: false
],
schema: [
type: :atom,
doc: false
]
]
@moduledoc """
`Ecto.ParameterizedType` for sets.
## Options
#{NimbleOptions.docs(opts_schema)}
## Examples
schema "users" do
field :email, :string
field :roles, Exandra.Set, type: :string
end
"""
use Ecto.ParameterizedType
@type t() :: MapSet.t()
@opts_schema NimbleOptions.new!(opts_schema)
# Made public for testing.
@doc false
def params(embed), do: %{embed: embed}
@impl Ecto.ParameterizedType
def type(_opts), do: :exandra_set
@impl Ecto.ParameterizedType
def init(opts) do
opts
|> NimbleOptions.validate!(@opts_schema)
|> Map.new()
end
@impl Ecto.ParameterizedType
def cast(nil, _), do: {:ok, MapSet.new()}
def cast({op, val}, opts) when op in [:add, :remove] do
case cast(val, opts) do
{:ok, casted} -> {:ok, {op, casted}}
other -> other
end
end
def cast(%MapSet{} = set, opts), do: set |> MapSet.to_list() |> cast(opts)
def cast(list, %{type: type}) when is_list(list) do
casted =
Enum.reduce_while(list, [], fn elem, acc ->
case Ecto.Type.cast(type, elem) do
{:ok, casted} -> {:cont, [casted | acc]}
err -> {:halt, err}
end
end)
if is_list(casted), do: {:ok, MapSet.new(casted)}, else: casted
end
def cast(val, %{type: type}) do
case Ecto.Type.cast(type, val) do
{:ok, casted} -> {:ok, MapSet.new([casted])}
err -> err
end
end
def cast(_key, _val), do: :error
@impl Ecto.ParameterizedType
def load(%MapSet{} = mapset, _loader, %{type: type}) do
loaded =
Enum.reduce_while(mapset, [], fn elem, acc ->
case Ecto.Type.cast(type, elem) do
{:ok, loaded} -> {:cont, [loaded | acc]}
err -> {:halt, err}
end
end)
if is_list(loaded), do: {:ok, MapSet.new(loaded)}, else: :error
end
def load(nil, _, _), do: {:ok, %MapSet{}}
def load(_field_name, loader, field) do
load(%MapSet{}, loader, field)
end
@impl Ecto.ParameterizedType
def dump(mapset, _dumper, _opts), do: {:ok, mapset}
@impl Ecto.ParameterizedType
def equal?({_, _}, _, _), do: false
def equal?(_, {_, _}, _), do: false
def equal?(nil, nil, _), do: true
def equal?(nil, data, _), do: Enum.empty?(data)
def equal?(data, nil, _), do: Enum.empty?(data)
def equal?(%MapSet{} = a, %MapSet{} = b, _), do: MapSet.equal?(a, b)
def equal?(_, _, _), do: false
# From Ecto.Type
@doc false
def embed_as(_format), do: :self
defimpl Jason.Encoder, for: MapSet do
def encode(set, opts) do
Jason.Encode.list(MapSet.to_list(set), opts)
end
end
end