defmodule Context do @moduledoc false defmacro __using__(opts) do repo = Keyword.fetch!(opts, :repo) quote do import Context, only: [context: 1, context: 2] Module.put_attribute(__MODULE__, :__repo__, unquote(repo)) end end @doc false defmacro context(schema, opts \\ []) do name = Keyword.get(opts, :name, parse_name(schema, __CALLER__)) funs = build_functions(name, opts) Enum.reduce(funs, [], fn {fun_name, arity}, acc -> fun = fun_name |> Atom.to_string() |> String.replace("_#{name}", "") |> String.to_existing_atom() [Kernel.apply(__MODULE__, :gen_fun, [{fun, arity}, name, schema]) | acc] end) end @spec parse_name(atom, any) :: String.t() defp parse_name(schema, caller) do schema |> Macro.expand_once(caller) |> Atom.to_string() |> String.split(".") |> List.last() |> Macro.underscore() end @spec build_functions(String.t(), keyword) :: [{atom, non_neg_integer}] defp build_functions(name, opts) do only = Keyword.get(opts, :only, default_functions(name)) except = Keyword.get(opts, :except, []) only -- except end @spec default_functions(String.t()) :: [{atom, non_neg_integer}] defp default_functions(name) do [ {:"get_#{name}", 1}, {:"get_#{name}!", 1}, {:"get_#{name}_by", 1}, {:"get_#{name}_by!", 1}, {:"find_#{name}", 1}, {:"find_#{name}_by", 1}, {:"create_#{name}", 1}, {:"update_#{name}", 2}, {:"delete_#{name}", 1}, {:"create_#{name}!", 1}, {:"update_#{name}!", 2}, {:"delete_#{name}!", 1}, {:"change_#{name}", 2} ] end @doc false @spec gen_fun(tuple, String.t(), atom) :: tuple def gen_fun(fun_and_arity, name, schema) def gen_fun({:get, 1}, name, schema) do quote do @doc """ Get a #{unquote(schema)} by ID ## Parameters - id: Valid ID ## Examples iex> #{__MODULE__}.get_#{unquote(name)}(1) %#{unquote(schema)}{} """ @spec unquote(:"get_#{name}")(String.t()) :: unquote(schema).t() | nil def unquote(:"get_#{name}")(id) when is_binary(id) do @__repo__.get(unquote(schema), id) end defoverridable [{unquote(:"get_#{name}"), 1}] end end def gen_fun({:get!, 1}, name, schema) do quote do @doc """ Find a #{unquote(schema)} by ID ## Parameters - id: Valid ID ## Examples iex> #{__MODULE__}.get_#{unquote(name)}!(1) %#{unquote(schema)}{} """ @spec unquote(:"get_#{name}!")(String.t()) :: unquote(schema).t() def unquote(:"get_#{name}!")(id) when is_binary(id) do @__repo__.get!(unquote(schema), id) end defoverridable [{unquote(:"get_#{name}!"), 1}] end end def gen_fun({:get_by, 1}, name, schema) do quote do @doc """ Find a #{unquote(schema)} by clauses ## Parameters - clauses: keyword list ## Examples iex> #{__MODULE__}.get_#{unquote(name)}_by(title: "title") %#{unquote(schema)}{} """ @spec unquote(:"get_#{name}_by")(keyword) :: unquote(schema).t() | nil def unquote(:"get_#{name}_by")(clauses) do @__repo__.get_by(unquote(schema), clauses) end defoverridable [{unquote(:"get_#{name}_by"), 1}] end end def gen_fun({:get_by!, 1}, name, schema) do quote do @doc """ Find a #{unquote(schema)} by clauses ## Parameters - clauses: keyword list ## Examples iex> #{__MODULE__}.get_#{unquote(name)}_by!(title: "title") %#{unquote(schema)}{} """ @spec unquote(:"get_#{name}_by!")(keyword) :: unquote(schema).t() def unquote(:"get_#{name}_by!")(clauses) do @__repo__.get_by!(unquote(schema), clauses) end defoverridable [{unquote(:"get_#{name}_by!"), 1}] end end def gen_fun({:find, 1}, name, schema) do quote do @doc """ Find a #{unquote(schema)} by ID ## Parameters - id: Valid ID ## Examples iex> #{__MODULE__}.find_#{unquote(name)}(1) {:ok, %#{unquote(schema)}{}} """ @spec unquote(:"find_#{name}")(String.t()) :: {:ok, unquote(schema).t()} | {:error, {unquote(schema), :not_found}} def unquote(:"find_#{name}")(id) when is_binary(id) do case @__repo__.get(unquote(schema), id) do nil -> {:error, {unquote(schema), :not_found}} term -> {:ok, term} end end defoverridable [{unquote(:"find_#{name}"), 1}] end end def gen_fun({:find_by, 1}, name, schema) do quote do @doc """ Find a #{unquote(schema)} by clauses ## Parameters - clauses: keyword list ## Examples iex> #{__MODULE__}.find_#{unquote(name)}_by(title: "title") {:ok, %#{unquote(schema)}{}} """ @spec unquote(:"find_#{name}_by")(keyword) :: {:ok, unquote(schema).t()} | {:error, {unquote(schema), :not_found}} def unquote(:"find_#{name}_by")(clauses) do case @__repo__.get_by(unquote(schema), clauses) do nil -> {:error, {unquote(schema), :not_found}} term -> {:ok, term} end end defoverridable [{unquote(:"find_#{name}_by"), 1}] end end def gen_fun({:create, 1}, name, schema) do quote do @doc """ Create a new #{unquote(schema)} ## Parameters - args: map ## Examples iex> #{__MODULE__}.create_#{unquote(name)}(%{}) {:ok, %#{unquote(schema)}{}} """ @spec unquote(:"create_#{name}")(map) :: {:ok, unquote(schema).t()} | {:error, Ecto.Changeset.t()} def unquote(:"create_#{name}")(args) when is_map(args) do changeset = unquote(schema).changeset(%unquote(schema){}, args) @__repo__.insert(changeset) end defoverridable [{unquote(:"create_#{name}"), 1}] end end def gen_fun({:create!, 1}, name, schema) do quote do @doc """ Create a new #{unquote(schema)} ## Parameters - args: map ## Examples iex> #{__MODULE__}.create_#{unquote(name)}(%{}) %#{unquote(schema)}{} """ @spec unquote(:"create_#{name}!")(map) :: unquote(schema).t() def unquote(:"create_#{name}!")(args) when is_map(args) do changeset = unquote(schema).changeset(%unquote(schema){}, args) @__repo__.insert!(changeset) end defoverridable [{unquote(:"create_#{name}!"), 1}] end end def gen_fun({:update, 2}, name, schema) do quote do @doc """ Update an existing #{unquote(schema)} ## Parameters - struct: %#{unquote(schema)}{} - args: map ## Examples iex> #{__MODULE__}.update_#{unquote(name)}!(%#{unquote(schema)}{}, %{}) {:ok, %#{unquote(schema)}{}} """ @spec unquote(:"update_#{name}")(unquote(schema).t(), map) :: {:ok, unquote(schema).t()} | {:error, Ecto.Changeset.t()} def unquote(:"update_#{name}")(%unquote(schema){} = struct, args) when is_map(args) do struct = case Keyword.has_key?(unquote(schema).__info__(:functions), :preload) do true -> @__repo__.preload(struct, apply(unquote(schema), :preload, [])) false -> struct end changeset = unquote(schema).changeset(struct, args) @__repo__.update(changeset) end defoverridable [{unquote(:"update_#{name}"), 2}] end end def gen_fun({:update!, 2}, name, schema) do quote do @doc """ Update an existing #{unquote(schema)} ## Parameters - struct: %#{unquote(schema)}{} - args: map ## Examples iex> #{__MODULE__}.update_#{unquote(name)}!(%#{unquote(schema)}{}, %{}) %#{unquote(schema)}{} """ @spec unquote(:"update_#{name}!")(unquote(schema).t(), map) :: unquote(schema).t() def unquote(:"update_#{name}!")(%unquote(schema){} = struct, args) when is_map(args) do struct = case Keyword.has_key?(unquote(schema).__info__(:functions), :preload) do true -> @__repo__.preload(struct, apply(unquote(schema), :preload, [])) false -> struct end changeset = unquote(schema).changeset(struct, args) @__repo__.update!(changeset) end defoverridable [{unquote(:"update_#{name}!"), 2}] end end def gen_fun({:delete, 1}, name, schema) do quote do @doc """ Delete an existing #{unquote(schema)} ## Parameters - struct: %#{unquote(schema)}{} ## Examples iex> #{__MODULE__}.delete_#{unquote(name)}(%#{unquote(name)}{}) {:ok, %#{unquote(schema)}{}} """ @spec unquote(:"delete_#{name}")(unquote(schema).t()) :: {:ok, unquote(schema).t()} | {:error, Ecto.Changeset.t()} def unquote(:"delete_#{name}")(%unquote(schema){} = struct) do @__repo__.delete(struct) end defoverridable [{unquote(:"delete_#{name}"), 1}] end end def gen_fun({:delete!, 1}, name, schema) do quote do @doc """ Delete an existing #{unquote(schema)} ## Parameters - struct: %#{unquote(schema)}{} ## Examples iex> #{__MODULE__}.delete_#{unquote(name)}(%#{unquote(name)}{}) {:ok, %#{unquote(schema)}{}} """ @spec unquote(:"delete_#{name}!")(unquote(schema).t()) :: unquote(schema).t() def unquote(:"delete_#{name}!")(%unquote(schema){} = struct) do @__repo__.delete(struct) end defoverridable [{unquote(:"delete_#{name}!"), 1}] end end def gen_fun({:change, 2}, name, schema) do quote do @doc """ Returns an #{unquote(schema)} changeset ## Examples iex> #{__MODULE__}.change_#{unquote(name)}(%#{__MODULE__}{}, %{}) %Ecto.Changeset{} """ @spec unquote(:"change_#{name}")(map) :: Ecto.Changeset.t() def unquote(:"change_#{name}")(%unquote(schema){} = schema, args \\ %{}) do unquote(schema).changeset(schema, args) end defoverridable [{unquote(:"change_#{name}"), 2}] end end end