Packages
one_piece_commanded
0.21.1
0.26.0
0.25.2
0.25.1
0.25.0
0.24.0
0.23.0
0.22.0
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.0
0.16.0
0.15.2
0.15.1
0.15.0
0.14.0
0.13.0
0.12.0
0.11.1
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.0
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.0
0.5.0
0.4.1
0.4.0
0.3.0
0.2.1
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
Extend Commanded package
Current section
Files
Jump to
Current section
Files
lib/one_piece/commanded/value_object.ex
defmodule OnePiece.Commanded.ValueObject do
@moduledoc """
Defines "Value Object" modules.
"""
alias Ecto.Changeset
@doc """
Converts the module into an `Ecto.Schema` and add factory functions to create structs.
## Using
- `Ecto.Schema`
- `Ecto.Type`
## Derives
- `Jason.Encoder`
## Usage
defmodule MyValueObject do
use OnePiece.Commanded.ValueObject
embedded_schema do
field :title, :string
# ...
end
end
{:ok, my_value} = MyValueObject.new(%{title: "Hello, World!"})
"""
@spec __using__(opts :: []) :: any()
defmacro __using__(_opts \\ []) do
quote do
alias OnePiece.Commanded.ValueObject
use Ecto.Schema
use Ecto.Type
@derive Jason.Encoder
@primary_key false
@before_compile OnePiece.Commanded.ValueObject
@doc """
Creates a `t:t/0`.
"""
@spec new(attrs :: map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()}
def new(attrs) do
ValueObject.__new__(__MODULE__, attrs)
end
@doc """
Creates a `t:t/0`.
"""
@spec new!(attrs :: map()) :: %__MODULE__{}
def new!(attrs) do
ValueObject.__new__!(__MODULE__, attrs)
end
@doc """
Returns an `t:Ecto.Changeset.t/0` for a given `t:t/0` value object.
"""
@spec changeset(message :: %__MODULE__{}, attrs :: map()) :: Ecto.Changeset.t()
def changeset(message, attrs) do
ValueObject.__changeset__(message, attrs)
end
def type, do: :map
def cast(value) when is_struct(value, __MODULE__), do: {:ok, value}
def cast(value) when is_map(value) do
with {:error, _} <- new(value), do: :error
end
def cast(_), do: :error
def load(data) when is_map(data) do
with {:error, _} <- new(data), do: :error
end
def load(_), do: :error
def dump(value) when is_struct(value, __MODULE__), do: {:ok, Map.from_struct(value)}
def dump(_), do: :error
defoverridable new: 1, new!: 1, changeset: 2
end
end
defmacro __before_compile__(env) do
enforced_keys = get_enforced_keys(env)
quote unquote: false, bind_quoted: [enforced_keys: enforced_keys] do
def __enforced_keys__ do
unquote(enforced_keys)
end
for the_key <- enforced_keys do
def __enforced_keys__?(unquote(the_key)) do
true
end
end
def __enforced_keys__?(_) do
false
end
end
end
defp get_enforced_keys(env) do
enforce_keys = Module.get_attribute(env.module, :enforce_keys) || []
enforce_keys ++ get_primary_key_name(env)
end
defp get_primary_key_name(env) do
case Module.get_attribute(env.module, :primary_key) do
{field_name, _, _} -> [field_name]
_ -> []
end
end
def __new__(struct_module, attrs) do
struct_module
|> apply_changeset(attrs)
|> Changeset.apply_action(:new)
end
def __new__!(struct_module, attrs) do
struct_module
|> apply_changeset(attrs)
|> Changeset.apply_action!(:new)
end
def __changeset__(%struct_module{} = message, attrs) do
embeds = struct_module.__schema__(:embeds)
fields = struct_module.__schema__(:fields)
changeset =
message
|> Changeset.cast(from_struct(attrs), fields -- embeds)
|> Changeset.validate_required(struct_module.__enforced_keys__() -- embeds)
Enum.reduce(
embeds,
changeset,
&cast_embed(&1, &2, struct_module)
)
end
defp cast_embed(field, changeset, struct_module) do
Changeset.cast_embed(changeset, field, required: struct_module.__enforced_keys__?(field))
end
defp from_struct(value) when is_struct(value) do
# https://github.com/elixir-ecto/ecto/issues/4168
Map.from_struct(value)
end
defp from_struct(value), do: value
defp apply_changeset(struct_module, attrs) do
struct(struct_module)
|> struct_module.changeset(attrs)
end
end