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 family. Pairs well with ecto_context for database-backed contexts — both share the same list / get / get_by query conventions.

Installation

def deps do
  [{:static_context, "~> 0.1"}]
end

Usage

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
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
listlist() All entries via entries/0
list_bylist_by(clauses) Filter with clause key validation
list_forlist_for(assoc, id) Filter by assoc_id foreign key
getget(id) Returns nil if not found
get!get!(id) Raises if not found
get_byget_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:

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:

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:

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:

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