Once.Prefixed (Once v1.0.0)
View SourceA variant of Once that adds a prefix to IDs, which makes them recognizable and self-documenting. This is useful for debugging, API clarity and type safety.
A prefixed ID looks like "usr_AV7m9gAAAAU" or "prod_123" - a human-readable prefix followed by the actual ID value. Note that prefixed IDs are always binaries - using :unsigned or :signed as :ex_format results in numeric strings like "prod_123".
Like Once itself, you can convert between formats using to_format/4. For more details on ID generation, formats, and options, see Once.
Usage
In your schemas, specify the prefix:
schema "users" do
field :id, Prefixed, prefix: "usr_", autogenerate: true
field :external_id, Prefixed, prefix: "usr_"
endAutogenerated IDs will include the prefix. Values in your application will look like "usr_AV7m9gAAAAU".
Prefix persistence
By default (:persist_prefix is false), the prefix exists only in Elixir - it's stripped before storing in the database and re-added when loading. This keeps your database columns compact (64-bit integers or binaries) while providing readability in your application.
# With persist_prefix: false (default)
field :id, Prefixed, prefix: "usr_"
# In Elixir: "usr_AV7m9gAAAAU"
# In database: stored as integer or binary without prefixWhen :persist_prefix is true, the full prefixed string is stored in the database. This sacrifices storage efficiency for database-level readability and grep-ability:
# With persist_prefix: true
field :external_id, Prefixed, prefix: "usr_", persist_prefix: true, db_format: :url64
# In Elixir: "usr_AV7m9gAAAAU"
# In database: "usr_AV7m9gAAAAU" (full string stored)Prefix persistence requires string-compatible formats
When persist_prefix: true, you must use :raw, :hex, hex32 or :url64 as :db_format. Integer formats (:signed, :unsigned) cannot store string prefixes.
Note that values in your Elixir application always have the prefix, regardless of the :persist_prefix setting. Cast only accepts prefixed input - unprefixed values will be rejected.
Options
:prefix(required) the string prefix to prepend to IDs, for example"usr_"or"prod_":persist_prefixwhether to store the prefix in the database (defaultfalse). Whentrue, requires:db_formatto be:raw,:hex, or:url64
All Once options are also supported. See Once.init_opt/0 for details.
Examples
# Basic usage with default settings (prefix not persisted)
field :user_id, Prefixed, prefix: "usr_"
# With custom formats
field :product_id, Prefixed, prefix: "prod_", ex_format: :hex, db_format: :raw
# With persisted prefix (stored in database)
field :external_id, Prefixed, prefix: "ext_", persist_prefix: true, db_format: :url64
# With numeric format for APIs
field :order_id, Prefixed, prefix: "ord_", ex_format: :signed, db_format: :signed
Summary
Types
Options to initialize Once.Prefixed.
Functions
Transform a prefixed ID between different formats while preserving the prefix.
Same as to_format/4 but raises on error.
Types
@type init_opt() :: Once.init_opt() | {:prefix, binary()} | {:persist_prefix, boolean()}
Options to initialize Once.Prefixed.
:prefix(required) the string prefix to prepend to IDs, for example"usr_"or"prod_":persist_prefixwhether to store the prefix in the database (defaultfalse). Whentrue, requires:db_formatto be:raw,:hex, or:url64
All Once options are also supported. See Once.init_opt/0 for details.
Functions
@spec to_format(binary(), binary(), Once.format(), [Once.to_format_opt()]) :: {:ok, binary()} | :error
Transform a prefixed ID between different formats while preserving the prefix.
This works like Once.to_format/3 but handles the prefix automatically. The prefix must match the second argument.
Note that this function does not return integers when converting to :signed or :unsigned, but only numeric strings like "prfx_123".
Options
:parse_intparse numeric strings like"123". Will give unexpected results with all-int hex/url64 inputs.
Examples
iex> Prefixed.to_format("prfx_-1", "prfx_", :url64, parse_int: true)
{:ok, "prfx___________8"}
iex> Prefixed.to_format("prfx___________8", "prfx_", :raw)
{:ok, <<"prfx_", 255, 255, 255, 255, 255, 255, 255, 255>>}
iex> Prefixed.to_format(<<"prfx_", 255, 255, 255, 255, 255, 255, 255, 255>>, "prfx_", :unsigned)
{:ok, "prfx_18446744073709551615"}
iex> Prefixed.to_format("prfx_18446744073709551615", "prfx_", :hex, parse_int: true)
{:ok, "prfx_ffffffffffffffff"}
iex> Prefixed.to_format("prfx_ffffffffffffffff", "prfx_", :hex32)
{:ok, "prfx_vvvvvvvvvvvvu"}
iex> Prefixed.to_format("prfx_vvvvvvvvvvvvu", "prfx_", :signed)
{:ok, "prfx_-1"}
iex> Prefixed.to_format("wrong_AAAAAAAAAAA", "usr_", :unsigned)
:error
iex> Prefixed.to_format("AAAAAAAAAAA", "usr_", :unsigned)
:error
@spec to_format!(binary(), binary(), Once.format(), [Once.to_format_opt()]) :: binary()
Same as to_format/4 but raises on error.
Examples
iex> "usr_AAAAAAAAAAA"
...> |> Prefixed.to_format!("usr_", :unsigned)
...> |> Prefixed.to_format!("usr_", :hex, parse_int: true)
...> |> Prefixed.to_format!("usr_", :signed)
...> |> Prefixed.to_format!("usr_", :raw, parse_int: true)
...> |> Prefixed.to_format!("usr_", :hex32)
...> |> Prefixed.to_format!("usr_", :url64)
"usr_AAAAAAAAAAA"
iex> Prefixed.to_format!("usr_AAAAAAAAAAA", "wrong_", :signed)
** (ArgumentError) value could not be parsed: "usr_AAAAAAAAAAA"
iex> Prefixed.to_format!("AAAAAAAAAAA", "usr_", :signed)
** (ArgumentError) value could not be parsed: "AAAAAAAAAAA"