# StaticContext
In-memory lookup data with uniform query API.
For data that lives in code, not the database — statuses, categories, types,
roles, or any fixed set of values. Define your entries as structs, declare
which query functions you need, and `static_context` generates them at
compile time. String ids throughout, matching DB storage and form values
with no atom/string boundary to cross.
Part of the [ExFoundry](https://github.com/exfoundry) family.
Pairs well with [`ecto_context`](https://github.com/exfoundry/ecto_context)
for database-backed contexts — both share the same `list` / `get` / `get_by`
query conventions.
## Installation
```elixir
def deps do
[{:static_context, "~> 0.1"}]
end
```
## Usage
```elixir
defmodule MyApp.Categories do
import StaticContext
alias MyApp.Category
static_context struct: Category do
list()
list_by()
list_for()
get()
get!()
get_by()
get_by!()
end
def entries do
[
%Category{id: "electronics", name: "Electronics", active: true},
%Category{id: "clothing", name: "Clothing", active: true},
%Category{id: "archived", name: "Archived", active: false}
]
end
end
```
```elixir
Categories.list()
#=> [%Category{id: "electronics", ...}, ...]
Categories.get!("electronics")
#=> %Category{id: "electronics", name: "Electronics", active: true}
Categories.list_by(active: true)
#=> [%Category{id: "electronics", ...}, %Category{id: "clothing", ...}]
```
## Generated functions
| Function | Signature | Notes |
|------------|----------------------|-----------------------------------------|
| `list` | `list()` | All entries via `entries/0` |
| `list_by` | `list_by(clauses)` | Filter with clause key validation |
| `list_for` | `list_for(assoc, id)`| Filter by `assoc_id` foreign key |
| `get` | `get(id)` | Returns nil if not found |
| `get!` | `get!(id)` | Raises if not found |
| `get_by` | `get_by(clauses)` | First match or nil |
| `get_by!` | `get_by!(clauses)` | First match or raises |
`get` and `get!` enforce string ids with a `when is_binary(id)` guard —
passing an atom raises `FunctionClauseError` at the call site.
## Ecto integration
`static_belongs_to` bridges static lookup modules with Ecto schemas. It stores
the id as a plain string in the database and resolves the full struct on load:
```elixir
defmodule MyApp.Article do
use Ecto.Schema
import StaticContext.Schema
schema "articles" do
field :title, :string
static_belongs_to :category, MyApp.Categories
end
end
```
This expands to:
```elixir
field :category_id, :string
field :category, StaticContext.Type, module: MyApp.Categories, source: :category_id
```
Use `category_id` in changesets and forms. The virtual `category` field is
populated automatically when the record is loaded from the database.
## Testing with ExMachina
Since static entries live in code, not the database, factories only need the
string id. Use ExMachina's lazy attributes to resolve the full struct
automatically — the function receives the struct being built and looks up
the entry by its id:
```elixir
def article_factory do
%Article{
title: sequence(:title, &"Article #{&1}"),
category_id: "electronics",
category: &Categories.get!(&1.category_id),
status_id: "draft",
status: &Statuses.get!(&1.status_id)
}
end
```
No need to `insert` or `build` static entries — they already exist in code.
Set the `_id` field, and the lazy attribute resolves the struct. Override
in tests as usual:
```elixir
insert(:article, category_id: "clothing", status_id: "published")
```
## How it works
Each function declaration maps to an EEx template in
`priv/templates/static_context/`. At compile time the macro renders the
template and injects the result into the calling module via
`Code.string_to_quoted!/1`. No runtime overhead — the generated functions
are plain Elixir operating on the list returned by your `entries/0` function.
## License
MIT