Packages

The implementation of the `Agent` backing up any container implementing `Access`. The default one is map. All the handlers are exposed to ease the implementation.

Current section

Files

Jump to
agency lib agency.ex
Raw

lib/agency.ex

defmodule Agency do
@moduledoc """
`Agency` is an abstraction layer above `Agent` allowing to use any
container supporting `Access` behind and simplifying the client API
handling.
`Agency` itself implements `Access` behaviour, making it possible to
embed instances in the middle of `Access.keys` chains.
In a nutshell, `Agency` backs up the `Agent` holding a container.
All the standard CRUD-like calls are done through containers’
`Access` implementation, allowing transparent shared access.
The set of `before_***/1` and `after_***/1` functions are introduced,
so that the main `Agent` feature distinguishing it from the standard
`GenServer` holding state—namely, a separation of client and server
APIs—is exposed transparently to the consumers.
Consider the following example.
```elixir
defmodule MyAgent do
use Agency, into: %{} # default
def after_get(value) do
value + 1
end
...
end
```
The above code introduces an `Agent` backing up `Map` and
exposes the standard CRUD-like functionality. After the value
would be got from the server API, it’d be increased by `1`
and returned to the consumer.
### Options
`use Agency` accepts two options:
- `into: Access.t()` the container to be used by `Agent`
- `data: map() | keyword()` the static data to be held by the instances
---
One might also pass any struct, or whatever else implementing
`Access` as `into:` option to be used as an `Agent` container.
"""
@type key :: any()
@type keys :: [key()]
@type keyz :: key() | keys()
@type value :: any()
@functions [
get: 1,
get_and_update: 2,
pop: 1,
put: 2,
update: 2
]
@doc false
def __functions__, do: @functions
# callbacks
@doc """
The callback that is called from all the interface
methods (but `this/0`) right before the `Agent`
is to be called.
Specific handlers take precedence over this one.
"""
@callback before_all(key()) :: key()
Enum.each(@functions, fn {fun, arity} ->
@doc """
The callback that is called from `#{fun}/#{arity}` right before
the `Agent` is to be called, passing key as an argument.
It should return a modified key. Note that the `key` here
is _always_ represented a list, even if the single value
was passed to the interface function.
"""
@callback unquote(:"before_#{fun}")(key) :: key when key: keys()
@doc """
The callback that is called from `#{fun}/#{arity}` right after
the `Agent` has returned the value, passing this value as
a parameter.
"""
@callback unquote(:"after_#{fun}")(value) :: value when value: value()
end)
@doc """
The callback that is called from `this/0` right after
the `Agent` has returned the value, passing this value as
a parameter.
"""
@callback after_this(value()) :: Access.t()
@doc """
Creates an `Agent` module.
If the module is already loaded, this is a no-op.
"""
@spec agent!(name :: binary() | atom()) :: module()
def agent!(name, opts \\ [])
def agent!(name, opts) when is_binary(name),
do: agent!(Module.concat(__MODULE__, String.capitalize(name)), opts)
def agent!(name, opts) when is_atom(name) do
opts = Macro.escape(opts)
case Code.ensure_compiled(name) do
{:module, module} ->
module
{:error, _reason} ->
{:module, module, _, _} =
Module.create(
name,
quote(do: use(Agency.Scaffold, unquote(opts))),
Macro.Env.location(__ENV__)
)
module
end
end
@doc false
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
use Agency.Scaffold, opts
end
end
end