SchemaCache

Hex.pmDocsCIcodecov

An Ecto-aware caching library providing cache-aside and write-through abstractions with automatic invalidation.

SchemaCache understands the relationship between your Ecto schemas and cached query results. When a schema is mutated, it knows exactly which cached values are affected and can evict or update them automatically.

Installation

Add schema_cache to your list of dependencies in mix.exs:

def deps do
  [
    {:schema_cache, "~> 0.1.0"}
  ]
end

Add SchemaCache.Supervisor to your application supervision tree:

children = [
  {SchemaCache.Supervisor, adapter: SchemaCache.Adapters.ETS},
  # ... other children
]

Quick Start

SchemaCache provides four core operations: read, create, update, and delete.

Cache-Aside

On a cache miss, SchemaCache invokes your callback to fetch from the source, caches the result, and records which schemas appear in it for later eviction.

# Cache a single record
{:ok, user} =
  SchemaCache.read("find_user", %{id: 5}, :timer.minutes(15), fn ->
    MyApp.Users.find(%{id: 5})
  end)

# Cache a collection
users =
  SchemaCache.read("users", %{active: true}, :timer.minutes(5), fn ->
    MyApp.Users.all(%{active: true})
  end)

Create

After inserting a new record, evict all cached collections for that schema type so they refresh on the next read and include the new record.

{:ok, new_user} =
  SchemaCache.create(fn ->
    %User{}
    |> User.changeset(%{name: "Bob", email: "bob@example.com"})
    |> Repo.insert()
  end)

Update with Eviction (default)

After an update, evict all cache keys that reference the mutated schema instance. The next read will fetch fresh data from the source.

{:ok, updated_user} =
  SchemaCache.update(fn ->
    MyApp.Users.update_user(user, %{name: "Alice"})
  end)

Update with Write-Through

Update all cached values referencing the schema in place, avoiding cache misses entirely. Use when your application can't serve stale results after a write.

{:ok, updated_user} =
  SchemaCache.update(
    fn -> MyApp.Users.update_user(user, %{name: "Alice"}) end,
    strategy: :write_through
  )

Delete

After a deletion, evict all cache keys that reference the deleted schema instance.

{:ok, deleted_user} =
  SchemaCache.delete(fn -> Repo.delete(user) end)

How It Works

SchemaCache maintains a reverse index from Ecto schemas to cache keys (SMKES). When a query result is cached, it records which schemas appear in that result. On mutation, it looks up all affected cache keys and evicts or updates them.

For a detailed explanation, see the Architecture Guide.

Using with ElixirCache

ElixirCache modules work out of the box. No wrapper module needed. SchemaCache auto-detects ElixirCache modules and translates API signatures automatically:

children = [
  MyApp.Cache,
  {SchemaCache.Supervisor, adapter: MyApp.Cache}
]

For Redis-backed ElixirCache modules, SchemaCache also auto-detects native set operations via command/1 with zero configuration. See the ElixirCache Integration Guide for details.

Documentation

Contributing

Prerequisites

Local Setup

# Clone the repository
git clone https://github.com/BobbieBarker/schema_cache.git
cd schema_cache

# Install dependencies
mix deps.get

# Create and migrate the test database
MIX_ENV=test mix ecto.setup

# Run the test suite
mix test

Running Redis Tests

Redis adapter tests require a running Redis instance. You can start one with docker-compose:

docker-compose up -d redis
mix test

Tests that require Redis will automatically skip if Redis is not available.

Code Quality

# Linting
mix credo

# Type checking
mix dialyzer

# Test coverage
mix coveralls

Pull Requests

  1. Fork the repository and create your branch from main.
  2. Write tests for any new functionality.
  3. Ensure mix test, mix credo, and mix dialyzer pass.
  4. Open a pull request with a clear description of the change.

License

MIT License. See LICENSE for details.