Skip to main content

lib/skuld/repo/ecto.ex

# Compile-time macro for generating a Plain implementation of Port.Repo
# that delegates to a specific Ecto Repo module.
#
# ## Usage
#
#     defmodule MyApp.Repo.Port do
#       use Skuld.Repo.Ecto, repo: MyApp.Repo
#     end
#
#     # Then wire it up:
#     comp
#     |> Port.with_handler(%{Port.Repo => MyApp.Repo.Port})
#     |> Comp.run!()
#
defmodule Skuld.Repo.Ecto do
  @moduledoc """
  Macro for generating a `Port.Repo.Behaviour` implementation that delegates
  to a specific Ecto Repo module.

  Each operation in the `Port.Repo` contract is implemented by calling
  the corresponding function on the configured Repo module with the
  same arguments.

  ## Usage

      defmodule MyApp.Repo.Port do
        use Skuld.Repo.Ecto, repo: MyApp.Repo
      end

  This generates a module satisfying `Skuld.Repo.Behaviour`
  with functions like:

      def insert(changeset), do: MyApp.Repo.insert(changeset)
      def update(changeset), do: MyApp.Repo.update(changeset)
      # ... etc.

  ## Handler Installation

      alias Skuld.Effects.Port

      comp
      |> Port.with_handler(%{Port.Repo => MyApp.Repo.Port})
      |> Comp.run!()
  """

  defmacro __using__(opts) do
    repo = Keyword.fetch!(opts, :repo)

    operations = Skuld.Repo.Contract.__callbacks__()

    delegations =
      Enum.map(operations, fn %{name: name, params: params, arity: arity} ->
        param_vars = Enum.map(params, fn p -> Macro.var(p, nil) end)

        quote do
          @impl true
          def unquote(name)(unquote_splicing(param_vars)) do
            unquote(repo).unquote(name)(unquote_splicing(param_vars))
          end

          defoverridable [{unquote(name), unquote(arity)}]
        end
      end)

    quote do
      @behaviour Skuld.Repo.Contract

      unquote_splicing(delegations)
    end
  end
end