Maps a preference key to the adapter configured to handle it.
Route format
A route is {pattern, adapter_module} or {pattern, adapter_module, adapter_opts}, where pattern is one of:
- an exact key like
"global.theme", matched by equality; - a wildcard like
"resource.*"— a prefix followed by a trailing"*", matching every key under that prefix (and the prefix itself); - the atom
:default, the fallback used when nothing else matches.
Wildcards are segmented by Backpex.Preferences.Key.parse/1, the same
function that segments keys. A pattern therefore addresses exactly the
segments a key is built from, including the colon-separated form used for
per-resource keys:
Backpex.Preferences.Keys.columns(MyApp.UserLive)
#=> "resource:MyApp.UserLive:columns"
# covered by any of:
"resource.*" # every resource
"resource:MyApp.UserLive:*" # just this resource
"resource:MyApp.UserLive:columns""*" is only meaningful as the final segment. Any other placement ("*"
alone, "resource.*.columns", "res*") is a configuration error and
raises when routes are first normalized (normally on the first preference
resolution) — it could never match a key, and a pattern that silently matches
nothing is worse than one that is rejected.
Match strategy
Longest-prefix-first: among the matching patterns the one with the most
segments wins, and an exact pattern beats a wildcard at the same depth.
Specificity alone decides, so a narrow route overrides a broad one no
matter which order they appear in config. :default wins only when
nothing else matches.
Configuration
config :backpex, Backpex.Preferences,
adapters: [
{"global.*", Backpex.Preferences.Adapters.Session, []},
{"resource.*", Backpex.Preferences.Adapters.Ecto,
repo: MyApp.Repo, schema: MyApp.Preference, scope_fields: [:user_id, :tenant_id]},
{:default, Backpex.Preferences.Adapters.Session, []}
],
scope: {MyAppWeb.PreferencesScope, :resolve, []}With no :adapters config the router falls back to a single
{:default, Backpex.Preferences.Adapters.Session, []} route, so the
zero-config behavior routes every key to the Session adapter. Once an
:adapters list is configured there is no implicit fallback; add an explicit
:default route unless the configured patterns cover every key.
Summary
Functions
Normalizes a raw route list, canonicalizing two-tuple entries to three-tuple form and validating shape.
Returns the matching {module, opts} for key, or raises if no route
(including :default) matches.
Returns the routes that can own prefix or keys beneath it, ordered from
broadest to most specific.
Loads the configured routes, falling back to a Session-adapter default when no config is set.
Types
Functions
Normalizes a raw route list, canonicalizing two-tuple entries to three-tuple form and validating shape.
Raises ArgumentError with a descriptive message for malformed entries: a
bad adapter module, an unusable pattern, or a wildcard that could never
match a key.
Returns the matching {module, opts} for key, or raises if no route
(including :default) matches.
This is the resolution entry point for point reads and writes. Subtree reads
use resolve_subtree/2 because exact routes and nested wildcards can carve
keys beneath a broader prefix into another adapter.
Examples
iex> routes = [
...> {"global.*", Backpex.Preferences.Adapters.Session, []},
...> {:default, Backpex.Preferences.Adapters.Session, []}
...> ]
iex> Backpex.Preferences.Router.resolve("global.theme", routes)
{Backpex.Preferences.Adapters.Session, []}
iex> routes = [
...> {"resource:MyApp.UserLive:*", MyApp.EctoAdapter, repo: MyApp.Repo},
...> {"resource.*", Backpex.Preferences.Adapters.Session, []}
...> ]
iex> Backpex.Preferences.Router.resolve("resource:MyApp.UserLive:columns", routes)
{MyApp.EctoAdapter, [repo: MyApp.Repo]}
Returns the routes that can own prefix or keys beneath it, ordered from
broadest to most specific.
A subtree can span adapters when an exact route or nested wildcard carves a
key out of a broader route. Backpex.Preferences.get_map/3 reads these
routes in order so the more specific route wins for the part it owns.
Examples
iex> routes = [
...> {"global.sidebar_section.blog", MyApp.DatabaseAdapter, []},
...> {"global.*", Backpex.Preferences.Adapters.Session, []}
...> ]
iex> Backpex.Preferences.Router.resolve_subtree("global.sidebar_section", routes)
[
{"global.*", Backpex.Preferences.Adapters.Session, []},
{"global.sidebar_section.blog", MyApp.DatabaseAdapter, []}
]
Loads the configured routes, falling back to a Session-adapter default when no config is set.