Skip to content

Commit f070b75

Browse files
committed
Add documentation for UUID and ULID primary key support; update model definitions and usage examples
1 parent b63fbfe commit f070b75

2 files changed

Lines changed: 129 additions & 13 deletions

File tree

docs/active-record-with-cql/defining-models.md

Lines changed: 127 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,38 +68,154 @@ end
6868

6969
### UUID Primary Keys
7070

71+
UUIDs (Universally Unique Identifiers) are 128-bit identifiers ideal for distributed systems where coordination between nodes isn't possible. CQL generates UUIDs automatically when creating records.
72+
73+
**Schema Definition:**
74+
75+
```crystal
76+
MyDB = CQL::Schema.define(:my_db, adapter: CQL::Adapter::SQLite, uri: "sqlite3://db.sqlite3") do
77+
table :sessions do
78+
primary :id, String, auto_increment: false # UUID stored as TEXT
79+
bigint :user_id
80+
text :token
81+
timestamps
82+
end
83+
end
84+
```
85+
86+
**Model Definition:**
87+
7188
```crystal
7289
class Session
7390
include CQL::ActiveRecord::Model(UUID)
74-
db_context AppDB, :sessions
91+
db_context MyDB, :sessions
7592
7693
property id : UUID?
77-
property user_id : Int32
78-
property token : String
79-
property expires_at : Time
94+
property user_id : Int64 = 0_i64
95+
property token : String = ""
96+
property created_at : Time?
97+
property updated_at : Time?
98+
99+
def initialize
100+
end
80101
81-
def initialize(@user_id : Int32, @token : String, @expires_at : Time)
102+
def initialize(@token : String, @user_id : Int64)
82103
end
83104
end
84105
```
85106

107+
**Usage:**
108+
109+
```crystal
110+
# Create a session - UUID is generated automatically
111+
session = Session.create!(
112+
token: "abc123",
113+
user_id: 1_i64,
114+
created_at: Time.utc,
115+
updated_at: Time.utc
116+
)
117+
118+
session.id # => UUID instance
119+
session.id!.to_s # => "550e8400-e29b-41d4-a716-446655440000"
120+
121+
# Find by UUID
122+
found = Session.find!(session.id!)
123+
124+
# UUIDs are unique across all records
125+
session1 = Session.create!(token: "token1", user_id: 1_i64, created_at: Time.utc, updated_at: Time.utc)
126+
session2 = Session.create!(token: "token2", user_id: 2_i64, created_at: Time.utc, updated_at: Time.utc)
127+
session1.id != session2.id # => true
128+
```
129+
130+
**UUID Characteristics:**
131+
132+
* **Format:** 8-4-4-4-12 hex characters (e.g., `550e8400-e29b-41d4-a716-446655440000`)
133+
* **Size:** 36 characters as string (with hyphens)
134+
* **Uniqueness:** Globally unique without coordination
135+
* **Use cases:** Distributed systems, public-facing IDs, merge-safe records
136+
86137
### ULID Primary Keys
87138

139+
ULIDs (Universally Unique Lexicographically Sortable Identifiers) combine the benefits of UUIDs with lexicographic sortability. They're ideal for time-ordered data like events, logs, or audit trails.
140+
141+
**Schema Definition:**
142+
143+
```crystal
144+
EventDB = CQL::Schema.define(:event_db, adapter: CQL::Adapter::SQLite, uri: "sqlite3://events.db") do
145+
table :events do
146+
primary :id, String, auto_increment: false # ULID stored as TEXT
147+
text :event_type
148+
text :payload
149+
timestamps
150+
end
151+
end
152+
```
153+
154+
**Model Definition:**
155+
88156
```crystal
89157
class Event
90-
include CQL::ActiveRecord::Model(ULID)
158+
include CQL::ActiveRecord::Model(String) # ULID uses String type
91159
db_context EventDB, :events
92160
93-
property id : ULID?
94-
property event_type : String
95-
property payload : JSON::Any
96-
property occurred_at : Time
161+
property id : String?
162+
property event_type : String = ""
163+
property payload : String = ""
164+
property created_at : Time?
165+
property updated_at : Time?
97166
98-
def initialize(@event_type : String, @payload : JSON::Any, @occurred_at : Time = Time.utc)
167+
def initialize
168+
end
169+
170+
def initialize(@event_type : String, @payload : String)
99171
end
100172
end
101173
```
102174

175+
**Usage:**
176+
177+
```crystal
178+
# Create an event - ULID is generated automatically
179+
event = Event.create!(
180+
event_type: "user.created",
181+
payload: "{\"user_id\": 1}",
182+
created_at: Time.utc,
183+
updated_at: Time.utc
184+
)
185+
186+
event.id # => "01ARZ3NDEKTSV4RRFFQ69G5FAV"
187+
event.id!.size # => 26 characters
188+
189+
# Find by ULID
190+
found = Event.find!(event.id!)
191+
192+
# ULIDs are sortable by creation time
193+
event1 = Event.create!(event_type: "first", payload: "{}", created_at: Time.utc, updated_at: Time.utc)
194+
sleep 1.millisecond
195+
event2 = Event.create!(event_type: "second", payload: "{}", created_at: Time.utc, updated_at: Time.utc)
196+
197+
event2.id! > event1.id! # => true (lexicographically sortable)
198+
199+
# Query events in chronological order using ID
200+
events = Event.order(:id).all # Ordered by creation time
201+
```
202+
203+
**ULID Characteristics:**
204+
205+
* **Format:** 26 Crockford Base32 characters (e.g., `01ARZ3NDEKTSV4RRFFQ69G5FAV`)
206+
* **Size:** 26 characters (more compact than UUID)
207+
* **Sortability:** Lexicographically sortable by creation time
208+
* **Uniqueness:** Unique with 80-bit randomness per millisecond
209+
* **Use cases:** Event sourcing, audit logs, time-series data, distributed systems needing time ordering
210+
211+
### Choosing Between Primary Key Types
212+
213+
| Type | Format | Sortable | Size | Best For |
214+
| --------------- | ------------------------ | -------- | --------- | -------------------------------- |
215+
| `Int32`/`Int64` | Sequential integer | Yes | 4-8 bytes | Simple apps, internal IDs |
216+
| `UUID` | Random hex | No | 36 chars | Distributed systems, public IDs |
217+
| `String` (ULID) | Base32 timestamp+random | Yes | 26 chars | Event logs, time-ordered data |
218+
103219
---
104220

105221
## Working with Attributes

src/ulid_compat.cr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# This provides a simple ULID-like implementation that works with modern Crystal
33
module CQL
44
module ULIDCompat
5-
ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
6-
TIME_LEN = 10
5+
ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
6+
TIME_LEN = 10
77
RANDOM_LEN = 16
88

99
# Generate a ULID-compatible string (26 characters)

0 commit comments

Comments
 (0)