AshPrefixedId
An Ash extension for working with prefixed IDs (e.g. user_CWzLBdFy2f1XhrtesFferY).
Inspired by Stripe's object IDs, this library lets you use human-readable, prefixed identifiers while storing standard UUIDs in the database.
Installation
Add ash_prefixed_id to your list of dependencies in mix.exs:
def deps do
[
{:ash_prefixed_id, "~> 0.2.0"}
]
end
Usage
Add the AshPrefixedId extension to your resource and configure a prefix:
defmodule App.Blog.Post do
use Ash.Resource,
domain: App.Blog,
data_layer: AshPostgres.DataLayer,
extensions: [AshPrefixedId]
prefixed_id do
prefix "post"
end
actions do
defaults [:read, :destroy, create: [:title], update: [:title]]
end
attributes do
uuid_primary_key :id
attribute :title, :string, public?: true
end
end
Post
|> Ash.Changeset.for_create(:create, %{title: "Hello world"})
|> Ash.create!()
#=> %Post{id: "post_CWzLBdFy2f1XhrtesFferY"}
Foreign Keys
belongs_to relationships automatically get the correct prefixed ID type:
relationships do
belongs_to :post, App.Blog.Post
# post_id is auto-created as App.Blog.Post.ObjectId
end
AnyPrefixedId
Register AnyPrefixedId as a custom type to accept prefixed IDs anywhere :uuid is used:
config :ash, custom_types: [uuid: AshPrefixedId.AnyPrefixedId]
PostgreSQL UUIDv7
Enable server-side UUIDv7 generation with AshPrefixedId.PostgresExtension:
defmodule MyApp.Repo do
use AshPostgres.Repo, otp_app: :my_app
def installed_extensions do
["uuid-ossp", "citext", AshPrefixedId.PostgresExtension]
end
end
Then in your resource:
prefixed_id do
prefix "post"
migration_default? true
end
Utility Functions
Inside Ash you only ever see prefixed IDs, so you rarely need these. They are
escape hatches for the boundaries of your system: validating external input,
or building raw SQL / talking to non-Ash code. Bang variants are the default;
the non-bang {:error, _} variants are for external input you expect might be
invalid.
# Decode a prefixed ID to the raw 16-byte UUID binary (for SQL fragments)
AshPrefixedId.to_uuid!("user_CWzLBdFy2f1XhrtesFferY")
#=> <<93, 68, 109, 8, ...>>
# Decode to a UUID string
AshPrefixedId.to_uuid_string!("user_CWzLBdFy2f1XhrtesFferY")
#=> "5d446d08-df6a-404d-a1e5-decc78429b3d"
# Non-bang variants for validating EXTERNAL input (e.g. an HTTP param)
AshPrefixedId.to_uuid(param) #=> {:ok, <<...>>} | {:error, :invalid_prefixed_id}
AshPrefixedId.to_uuid_string(param) #=> {:ok, "..."} | {:error, :invalid_prefixed_id}
# Encode a UUID binary back to a prefixed ID (prefix string or resource module)
AshPrefixedId.to_prefixed_id(uuid_binary, "user")
AshPrefixedId.to_prefixed_id(uuid_binary, MyApp.Accounts.User)
# Find which resource a prefixed ID belongs to
AshPrefixedId.find_resource_for_id(domains, "user_CWzLBdFy2f1XhrtesFferY")
# Detect duplicate prefixes across domains
AshPrefixedId.find_duplicate_prefixes(domains)
To accept both prefixed IDs and raw UUIDs transparently rather than converting
by hand, register AshPrefixedId.AnyPrefixedId as a custom type (see above).
For more detailed information, read the AshPrefixedId moduledoc.
Acknowledgments
This project is a fork of ash_object_ids by Randall Theuns. The original work laid the foundation for prefixed ID support in Ash.
License
MIT — see LICENSE for details.