Packages

Dependency injection for Elixir

Current section

Files

Jump to
dependency lib dependency.ex
Raw

lib/dependency.ex

defmodule Dependency do
@moduledoc """
Fuctions to build soft dependencies between modules. This is useful when you want to test with diffirent situations in test mode.
The resolution is dynamic in test mode (uses `Registry`). In dev and production modes, the dependency in compiled inline.
"""
alias Dependency.MissingError
@doc """
Starts the dependency registry.
This is done for you if you add `:dependency` to list of apps in your `mix.exs`.
Returns `{:ok, pid}`
"""
@spec start_link :: {:ok, pid()} | {:error, atom()}
def start_link do
Registry.start_link(keys: :unique, name: Dependency.Registry)
end
@doc """
Register an implementation for a module.
Returns `{:ok, module}`
## Examples
iex> Dependency.register(Foo, Bar)
{:ok, Bar}
"""
@spec register(module(), module()) :: {:ok, module()}
def register(mod, implementation) do
case Registry.register(Dependency.Registry, mod, implementation) do
{:ok, _pid} ->
{:ok, implementation}
{:error, {:already_registered, _pid}} ->
:ok = Registry.unregister(Dependency.Registry, mod)
register(mod, implementation)
end
end
@doc """
Resolve the implementation for a module.
Returns `module`
## Examples
iex> Dependency.register(Foo, Bar)
{:ok, Bar}
iex> Dependency.resolve(Foo)
Bar
"""
@spec resolve(module()) :: module()
defmacro resolve(name) do
quote do
if Mix.env() == :test do
Dependency.dynamically_resolve(unquote(name))
else
unquote(name)
end
end
end
@doc false
def dynamically_resolve(name) do
case Registry.lookup(Dependency.Registry, name) do
[{_pid, implementation}] ->
implementation
[] ->
raise MissingError, message: "dependency #{name} is not registered"
end
end
end