View Source ActiveMemory.Store (ActiveMemory v0.7.0)
The Store
store-api
Store API
Store.all/0Get all records storedStore.delete/1Delete the record providedStore.delete_all/0Delete all records storedStore.one/1Get one record matching either an attributes search ormatchqueryStore.select/1Get all records matching either an attributes search ormatchqueryStore.withdraw/1Get one record matching either an attributes search ormatchquery, delete the record and return itStore.write/1Write a record into the memmory table
concurrency
Concurrency
A Store is a GenServer, but the data functions above (all/0, one/1,
select/1, write/1, delete/1, withdraw/1, delete_all/0) are not
routed through that process and are not serialized by it. They are ordinary
module functions that run in the caller's process, delegating straight to the
table's adapter (and therefore to :ets/:mnesia). Concurrency is governed by
ETS/Mnesia themselves, so many processes read and write in parallel — the single
GenServer is not a bottleneck. Only lifecycle and metadata operations (init,
state/0, reload_seeds/0) actually use the GenServer.
These functions live on the GenServer module purely for organization: the
Store is the single place responsible for how the application talks to its
table, following the Single Responsibility Principle. See the
S.T.O.N.E principles for
the broader design philosophy.
expiry-ttl
Expiry (TTL)
When the Store's Table declares a ttl (see ActiveMemory.Table), records
expire automatically. Expiry is enforced in two ways: reads (one/1, select/1,
all/0, withdraw/1) never return an expired record, and the Store periodically
sweeps expired records to reclaim memory. The sweep cadence defaults to one minute
and can be set with the sweep_interval option (milliseconds):
defmodule MyApp.Tokens.Store do
use ActiveMemory.Store,
table: MyApp.Tokens.Token,
sweep_interval: :timer.seconds(30)
endThe sweep only runs when the table has a ttl; otherwise it is never scheduled.
seeding
Seeding
When starting a Store there is an option to provide a valid seed file and have the Store auto load seeds contained in the file.
defmodule MyApp.People.Store do
use ActiveMemory.Store,
table: MyApp.People.Person,
seed_file: Path.expand("person_seeds.exs", __DIR__)
end
before-init
Before init
All stores are GenServers and have init functions. While those are abstracted you can still specify methods to run during the init phase of the GenServer startup. Use the before_init keyword and add the methods as tuples with the arguments.
defmodule MyApp.People.Store do
use ActiveMemory.Store,
table: MyApp.People.Person,
before_init: [{:run_me, ["arg1", "arg2", ...]}, {:run_me_too, []}]
end
before_initand table recoveryFor ETS stores, the table is preserved across a store crash/restart by
ActiveMemory.TableHeir. On such a recovery seed files are not re-run, butbefore_initmethods always run, including on recovery. If abefore_initmethod writes records with unique or generated keys (for example auuid), running it again on recovery can create duplicates.How to handle this is left to the implementer. One option is to make any
before_initwrite follow a "find or create" pattern — check withone/1before callingwrite/1— so the method is idempotent across restarts:def run_me(args) do record = build_record(args) case one(%{key: record.key}) do {:ok, existing} -> {:ok, existing} {:error, :not_found} -> write(record) end end
initial-state
Initial State
All stores are GenServers and thus have a state. The default state is a map as such:
%{started_at: "date time when first started", table_name: MyApp.People.Person}This default state can be overwritten with a new state structure or values by supplying a method and arguments as a tuple to the keyword initial_state. The method must return {:ok, new_state}.
defmodule MyApp.People.Store do
use ActiveMemory.Store,
table: MyApp.People.Person,
initial_state: {:initial_state_method, ["arg1", "arg2", ...]}
end