Packages
nimble_ets
0.1.0
The very simple ETS wrapper simplifying cross-process ETS handling (like `Agent`, but `:ets`).
Retired package: Renamed - Renamed to easy_ets https://elixirforum.com/t/nimbleets-kv-store-for-zero-cost/21940
Current section
Files
Jump to
Current section
Files
lib/nimble_ets.ex
defmodule NimbleETS do
@moduledoc """
`NimbleETS` is a very thin simple ETS wrapper, simplifying the trivial usage of ETS
as key-value store. It is not intended to replicate `:ets` module functionality
by any mean. It might be used as a drop-in to avoid process-based `Agent` as
key-value store.
It exposes _only_ `CRUD` functionality of _ETS_, alongside with `Access` behaviour.
Built on top of `:ets`, it’s not distributed. Tables created are sets; `public`
and `named` by default. This might be changed by passing `{table_name, options}`
tuple instead of just table name to the initializer (see below.)
### Usage
There are two ways `NimbleETS` might be used: as a standalone module,
or as a module extension.
#### Standalone usage
{:ok, pid} = NimbleETS.Tables.start_link()
NimbleETS.new(MyApp.MyModuleToBeGenerated)
MyApp.MyModuleToBeGenerated.ets_put(:foo, 42)
#⇒ %MyApp.MyModuleToBeGenerated{table: MyApp.MyModuleToBeGenerated}
MyApp.MyModuleToBeGenerated.ets_get(:foo, 42)
#⇒ 42
term = %{data: MyApp.MyModuleToBeGenerated.ets_put(:value, 42)}
update_in(term, [:data, :value], fn _ -> "👍" end)
get_in(term, [:data, :value])
#⇒ "👍"
GenServer.stop(pid)
The table is actually managed by the `NimbleETS` application,
so it won’t be destroyed if the process called `NimbleETS.new/1` exits.
#### Module
defmodule MyApp.MyModuleBackedByTable do
use NimbleETS
end
MyApp.MyModuleBackedByTable.ets_put(:foo, 42)
MyApp.MyModuleBackedByTable.ets_get(:foo)
#⇒ 42
MyApp.MyModuleBackedByTable.ets_del(:foo)
MyApp.MyModuleBackedByTable.ets_get(:foo)
#⇒ 42
One might override `ets_table_name/0` in the module to change
the name of the table.
### Interface exported
`NimbleETS` exports the simplest possible interface for `CRUD` on purpose.
Whether one needs more sophisticated `:ets` operations, it’s still possible
through `%MyApp.MyModuleBackedByTable{}.table` (yes, it’s a struct underneath.)
The latter holds the reference to the respective `:ets` table.
```elixir
@doc "Updates the value in the table under the key passed"
@spec ets_put(key :: term(), value :: term()) :: NimbleETS.t()
@doc "Retrieves the value from the table stored under the key passed"
@spec ets_get(key :: term(), default :: any()) :: term()
@doc "Deletes the value from the table stored under the key passed"
@spec ets_del(key :: term()) :: NimbleETS.t()
@doc "Returns all the values from the table"
@spec ets_all() :: list()
```
### `Access` behaviour
Modules produced / updated by `NimbleETS` do support `Access` behaviour.
### [`Envío`](https://hexdocs.pm/envio) support
Modules produced / updated by `NimbleETS` do send broadcast messages
on both `:update` and `:delete` actions. See [`Envío`](https://hexdocs.pm/envio/envio.html#creating-a-subscriber) documentation on how to subscribe to them.
Each message is sent to two channels: `:all` (all the updates managed by `NimbleCSV`)
and the channel with the name equal to the name of the table updated.
"""
@doc """
Creates new ETS table(s) wrapper(s) based on definitions passed as a parameter.
_Examples:_
NimbleETS.new(MyApp.MyExistingModule)
NimbleETS.new([{MyApp.WithOptions, [:bag]}, MyApp.ToCreate])
For the full list of options please refer to
[`:ets.new/2`](http://erlang.org/doc/man/ets.html#new-2) documentation.
"""
defdelegate new(definitions), to: NimbleETS.Tables
##############################################################################
# Meta (use NimbleETS)
defmacro __using__(opts \\ []) do
table = opts[:table]
quote do
@table unquote(table) || __MODULE__
defstruct table: @table
@type t :: %__MODULE__{table: atom()}
@doc "Updates the value in the table under the key passed"
@spec ets_put(key :: term(), value :: term()) :: t()
def ets_put(key, value) do
NimbleETS.Tables.ets_del(ets_table_name(), key)
NimbleETS.Tables.ets_put(ets_table_name(), key, value)
publish(%{action: :update, key: key, value: value})
%__MODULE__{table: ets_table_name()}
end
@doc "Retrieves the value from the table stored under the key passed"
@spec ets_get(key :: term(), default :: any()) :: term()
def ets_get(key, default \\ nil),
do: NimbleETS.Tables.ets_get(ets_table_name(), key, default)
@doc "Deletes the value from the table stored under the key passed"
@spec ets_del(key :: term()) :: t()
def ets_del(key) do
NimbleETS.Tables.ets_del(ets_table_name(), key)
publish(%{action: :delete, key: key})
%__MODULE__{table: ets_table_name()}
end
@doc "Returns all the values from the table"
@spec ets_all() :: list()
def ets_all(),
do: NimbleETS.Tables.ets_all(ets_table_name())
@doc "The ETS table name to be used. Defaults to #{__MODULE__}."
def ets_table_name(), do: @table
defp publish(data) do
NimbleETS.Envio.broadcast(ets_table_name(), data)
NimbleETS.Envio.broadcast(:all, data)
end
defoverridable ets_table_name: 0
##########################################################################
### Access behaviour implementation
@behaviour Access
@doc false
@impl Access
def fetch(this, key), do: NimbleETS.Tables.ets_fetch(ets_table_name(), key)
@doc false
@impl Access
def get_and_update(this, key, function) do
value =
case fetch(this, key) do
{:ok, value} -> value
_ -> nil
end
case function.(value) do
:pop -> {value, this = ets_del(key)}
{_, updated} -> {value, this = ets_put(key, updated)}
end
end
@doc false
@impl Access
def pop(this, key) do
case fetch(this, key) do
{:ok, value} -> {value, this = ets_del(key)}
_ -> {nil, this}
end
end
end
end
end