lib/one_piece/commanded/command.ex

defmodule OnePiece.Commanded.Command do
  @moduledoc """
  Defines a module as a "Command". For more information about commands, please read the following:

  - [CQRS pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/cqrs)
  """

  alias OnePiece.Commanded.Command
  alias OnePiece.Commanded.Entity
  alias OnePiece.Commanded.Helpers

  @type t :: struct()

  @doc """
  Converts the module into an `Ecto.Schema`, and derive from `Jason.Encoder`.

  ## Usage

      defmodule MyCommand do
        use OnePiece.Commanded.Command, aggregate_identifier: :id

        embedded_schema do
          # ...
        end
      end
  """
  @spec __using__(opts :: [aggregate_identifier: atom()]) :: any()
  defmacro __using__(opts \\ []) do
    unless Keyword.has_key?(opts, :aggregate_identifier) do
      raise ArgumentError, "missing :aggregate_identifier key"
    end

    aggregate_identifier = Keyword.fetch!(opts, :aggregate_identifier)

    quote do
      use Ecto.Schema

      @typedoc """
      The key used to identify the aggregate.
      """
      @type aggregate_identifier_key :: unquote(aggregate_identifier)

      @aggregate_identifier_key unquote(aggregate_identifier)
      @primary_key {@aggregate_identifier_key, :string, autogenerate: false}
      @derive Jason.Encoder

      @doc """
      Creates a new `t:t/0` command.
      """
      @spec new(attrs :: map()) :: struct()
      def new(attrs) do
        Helpers.struct_from(attrs, __MODULE__)
      end

      @doc """
      Returns the aggregate identifier key.
      """
      @spec aggregate_identifier :: aggregate_identifier_key()
      def aggregate_identifier do
        @aggregate_identifier_key
      end

      @doc """
      Put the `value` into `t:t/0` aggregate identifier key.
      """
      @spec put_aggregate_id(command :: Command.t(), Entity.identity()) :: Command.t()
      def put_aggregate_id(command, value) do
        Map.put(command, @aggregate_identifier_key, value)
      end
    end
  end
end