View Source ActiveMemory.ActiveRepo (ActiveMemory v0.7.0)

The ActiveRepo

An ActiveRepo manages multiple ActiveMemory.Tables from a single process. It is the multi-table counterpart to ActiveMemory.Store (which manages a single table), giving you one supervised entry point and a unified API over many tables.

It is named ActiveRepo rather than Repo so it does not collide with an application's Ecto.Repo while keeping the familiar "repo" terminology.

defmodule MyApp.ActiveRepo do
  use ActiveMemory.ActiveRepo,
    tables: [
      MyApp.People.Person,
      {MyApp.Dogs.Dog, seed_file: Path.expand("dog_seeds.exs", __DIR__), before_init: [{:warm, []}]}
    ]
end

Add the ActiveRepo to your supervision tree like any other process:

children = [MyApp.ActiveRepo]

Tables may freely mix :ets and :mnesia adapters; each operation dispatches to the adapter configured on the given table.

activerepo-api

ActiveRepo API

Reads and withdraw take the table module as the first argument; writes and deletes infer the table from the struct.

  • ActiveRepo.all/1 Get all records stored in a table
  • ActiveRepo.delete/1 Delete the record provided
  • ActiveRepo.delete_all/1 Delete all records stored in a table
  • ActiveRepo.one/2 Get one record from a table matching an attributes search or match query
  • ActiveRepo.select/2 Get all records from a table matching an attributes search or match query
  • ActiveRepo.withdraw/2 Get, delete and return one record from a table
  • ActiveRepo.write/1 Write a record into its table

An operation for a struct or table that is not part of the ActiveRepo returns {:error, :unknown_table}.

concurrency

Concurrency

Like a Store, an ActiveRepo is a GenServer, but the data functions above are not routed through that process and are not serialized by it. They run in the caller's process and delegate straight to each table's adapter, so reads and writes execute with ETS/Mnesia concurrency — the single GenServer is not a bottleneck. Only lifecycle and metadata operations (init, state/0, reload_seeds/1) use the GenServer.

These functions live on the GenServer module purely for organization: the ActiveRepo is the single place responsible for how the application talks to its tables, following the Single Responsibility Principle. See the S.T.O.N.E principles for the broader design philosophy.

tables-and-per-table-options

Tables and per-table options

Each entry of tables: is either a table module or a {table, opts} tuple. The supported per-table options mirror the single-table ActiveMemory.Store:

  • seed_file a path to a seed file auto loaded when the table is first created
  • before_init methods (defined on the ActiveRepo) run during the table's setup

expiry-ttl

Expiry (TTL)

Any table whose ActiveMemory.Table declares a ttl expires its records automatically: reads never return an expired record, and the ActiveRepo periodically sweeps expired records from every ttl table it owns to reclaim memory. The sweep cadence defaults to one minute and can be set with the sweep_interval option (milliseconds). Tables without a ttl are left untouched, and the sweep is only scheduled when at least one table uses a ttl.

initial-state

Initial State

Like a Store, an ActiveRepo is a GenServer with state. The default state is:

%{started_at: "date time when first started", tables: [MyApp.People.Person, ...]}

Supply a {method, args} tuple to the initial_state keyword to override it; the method must return {:ok, new_state}.

resilience

Resilience

ETS tables created by an ActiveRepo get the same ActiveMemory.TableHeir protection as a Store: they survive an ActiveRepo crash and are reclaimed on restart, and seed files are not re-run on recovery. See ActiveMemory.Store for the before_init recovery caveat, which applies here as well.