Ecto.Repo by default doesn't allow overrides. So when you need to compose functions but maintain an API parity with the Ecto.Repo. You'd need to compose the functions in another module and things can get messy really fast that way.
Can't invoke super
== Compilation error on file lib/app/repo.ex ==
** (CompileError) lib/app/repo.ex:6: no super defined for all/2 in module App.Repo. Overridable functions available are:
(stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
(elixir) lib/kernel/parallel_compiler.ex:117: anonymous fn/4 in Kernel.ParallelCompiler.spawn_compilers/1
Sure you can solve this using service modules, but sometimes you might want some sort of functionality for every database operations.
With this you can easily compose some of the Repo functions but maintain an API parity without writing all the delegation functions 😎
With this utility module
defmodule ComposableRepo do
defmacro __using__(opts) do
quote bind_quoted: binding() do
@composable_repo Keyword.fetch!(opts, :composing)
@composable_functions @composable_repo.__info__(:functions)
@composable_functions
|> Enum.map(fn
{function, 0} ->
{function, []}
{function, arity} ->
{function, Enum.map(1..arity, &(Macro.var(:"arg_#{&1}", __MODULE__)))}
end)
|> Enum.map(fn {function, arguments} ->
defdelegate unquote(function)(unquote_splicing(arguments)), to: @composable_repo
end)
defoverridable @composable_functions
end
end
end
defmodule App.OriginalRepo do
use Ecto.Repo, otp_app: :my_app
end
defmodule App.ComposedRepo do
use ComposableRepo, composing: App.OriginalRepo
# Now you can override the repo functions to
# add maybe an event bus for schema inserts
def insert(struct_or_changeset, opts \\ []) do
transaction(fn ->
case super(struct_or_changeset, opts) do
{:ok, schema} ->
:ok = App.Endpoint.broadcast(:schema_inserts, schema.__struct__, schema)
schema
{:error, changeset} ->
rollback(changeset)
end
end)
end
end
Might wanna test it out on IEx
iex> App.Endpoint.subscribe(:schema_inserts)
iex> {:ok, user} = App.ComposedRepo.insert(%User{name: "imran"})
iex> user == App.ComposedRepo.one(User)
iex> flush()
With the Elixir 1.5, you can now disregard this and use the following instead