Composite Primary Keys
View SourceWhen working with resources that have composite primary keys (multiple fields that together form the unique identifier), AshJsonApi provides special support for encoding and decoding these keys in URLs.
Defining Composite Primary Keys
First, define your composite primary key in the JSON API configuration:
defmodule MyApp.Bio do
use Ash.Resource,
extensions: [AshJsonApi.Resource]
attributes do
attribute :author_id, :uuid, primary_key?: true, public?: true
attribute :category, :string, primary_key?: true, public?: true
attribute :content, :string, public?: true
end
json_api do
type "bio"
primary_key do
keys [:author_id, :category]
delimiter "|" # Use a delimiter that won't conflict with your data
end
end
end
Important Considerations for Delimiters
When choosing a delimiter, ensure it won't appear in your actual data:
- UUIDs contain dashes (
-
) - Don't use-
as a delimiter if any of your composite key fields are UUIDs - Safe alternatives:
|
,~
,::
, or other characters unlikely to appear in your data - Default delimiter: If not specified, AshJsonApi uses
-
as the default delimiter
Enabling Composite Key Parsing in Routes
To enable automatic parsing of composite primary keys in URL paths, you must opt-in by specifying the path_param_is_composite_key
option on your routes:
json_api do
type "bio"
primary_key do
keys [:author_id, :category]
delimiter "|"
end
routes do
base "/bios"
# Enable composite key parsing for the :id parameter
get :read, path_param_is_composite_key: :id
patch :update, path_param_is_composite_key: :id
delete :destroy, path_param_is_composite_key: :id
# Other routes that don't need composite key parsing
index :read
post :create
end
end
How It Works
With the above configuration:
- URL Format:
/bios/550e8400-e29b-41d4-a716-446655440000|sports
- Parsing: The
:id
parameter550e8400-e29b-41d4-a716-446655440000|sports
gets split by the|
delimiter - Mapping: The parts are mapped to the primary key fields in order:
author_id
=550e8400-e29b-41d4-a716-446655440000
category
=sports
- Filtering: The query is filtered to find the record with both
author_id
andcategory
matching
Example Usage
# Creating a bio
POST /bios
{
"data": {
"type": "bio",
"attributes": {
"author_id": "550e8400-e29b-41d4-a716-446655440000",
"category": "sports",
"content": "Author bio for sports category"
}
}
}
# Retrieving the bio using composite key
GET /bios/550e8400-e29b-41d4-a716-446655440000|sports
# Updating the bio
PATCH /bios/550e8400-e29b-41d4-a716-446655440000|sports
{
"data": {
"type": "bio",
"attributes": {
"content": "Updated bio content"
}
}
}
# Deleting the bio
DELETE /bios/550e8400-e29b-41d4-a716-446655440000|sports
Without Opt-In Parsing
If you don't specify path_param_is_composite_key
on a route, the path parameter will be treated as a regular single value, even if your resource has composite primary keys defined. This ensures backward compatibility and prevents unexpected behavior.
Error Handling
If the composite key format is invalid (wrong number of parts after splitting), AshJsonApi will return a 404 Not Found error with appropriate JSON:API error formatting.