Once.Prefixed (Once v1.0.0)

View Source

A 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_"
end

Autogenerated 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 prefix

When :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_prefix whether to store the prefix in the database (default false). When true, requires :db_format to 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.

Types

init_opt()

@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_prefix whether to store the prefix in the database (default false). When true, requires :db_format to be :raw, :hex, or :url64

All Once options are also supported. See Once.init_opt/0 for details.

Functions

to_format(value, prefix, format, opts \\ [])

@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_int parse 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

to_format!(value, prefix, format, opts \\ [])

@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"