rails-uuid-pk 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ARCHITECTURE.md +508 -0
- data/CHANGELOG.md +78 -0
- data/DEVELOPMENT.md +314 -0
- data/PERFORMANCE.md +417 -0
- data/README.md +43 -12
- data/SECURITY.md +321 -0
- data/lib/generators/rails_uuid_pk/add_opt_outs_generator.rb +183 -0
- data/lib/rails_uuid_pk/migration_helpers.rb +6 -2
- data/lib/rails_uuid_pk/mysql2_adapter_extension.rb +14 -56
- data/lib/rails_uuid_pk/railtie.rb +7 -2
- data/lib/rails_uuid_pk/sqlite3_adapter_extension.rb +8 -52
- data/lib/rails_uuid_pk/trilogy_adapter_extension.rb +39 -0
- data/lib/rails_uuid_pk/uuid_adapter_extension.rb +80 -0
- data/lib/rails_uuid_pk/version.rb +2 -2
- data/lib/rails_uuid_pk.rb +5 -0
- metadata +26 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 46d317c27d7ed27fb503bba3020d93e48f770e2492c8561c347544c9e8d4db4f
|
|
4
|
+
data.tar.gz: 39ed2abf06c8424ae2e431ee5cd09e20849a475c671266e6be7345768473bffd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eca0cdf854a9924f4268d3cf33d8c9b4c13061d569e9551e6ceb591873f406e5c451daccc26206b44ebcacd054a4ea4a7d486a32596790fd61a0ef61b5dd0380
|
|
7
|
+
data.tar.gz: 10852d080506a8305adf8c2c538a788d9f6790c2dec4770a7329f8cb2acaf95e66d2903daf8b69ac0753d6f16ec5c9e1d478e9841cc73088227b6f710711580f
|
data/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
This document outlines the architectural decisions, design principles, and technical trade-offs made in rails-uuid-pk. It serves as a guide for understanding why certain approaches were chosen and the implications of those decisions.
|
|
4
|
+
|
|
5
|
+
## Core Design Principles
|
|
6
|
+
|
|
7
|
+
### Zero-Configuration Philosophy
|
|
8
|
+
**Decision**: Automatic UUIDv7 primary keys for all models by default
|
|
9
|
+
- **Rationale**: Maximize ease of adoption for Rails developers while modernizing primary key usage
|
|
10
|
+
- **Implementation**: Railtie-based automatic inclusion in `ActiveRecord::Base` with opt-out mechanism
|
|
11
|
+
- **Impact**: Just add the gem to Gemfile - all models use UUIDv7 PKs, exceptions require explicit opt-out
|
|
12
|
+
|
|
13
|
+
### Database Agnosticism
|
|
14
|
+
**Decision**: Support PostgreSQL, MySQL, and SQLite with unified API
|
|
15
|
+
- **Rationale**: Enable database portability and CI/testing flexibility
|
|
16
|
+
- **Implementation**: Adapter-specific extensions with common interface
|
|
17
|
+
- **Impact**: Developers can switch databases without code changes
|
|
18
|
+
|
|
19
|
+
### App-Level UUID Generation
|
|
20
|
+
**Decision**: Generate UUIDs in application code, not database
|
|
21
|
+
- **Rationale**: Maximum compatibility across database versions and types
|
|
22
|
+
- **Implementation**: `before_create` callback using `SecureRandom.uuid_v7`
|
|
23
|
+
- **Trade-offs**: No database-native UUID functions, but universal compatibility
|
|
24
|
+
|
|
25
|
+
## Architectural Components
|
|
26
|
+
|
|
27
|
+
### 1. Core Modules
|
|
28
|
+
|
|
29
|
+
#### Concern (`lib/rails_uuid_pk/concern.rb`)
|
|
30
|
+
```ruby
|
|
31
|
+
module RailsUuidPk
|
|
32
|
+
module HasUuidv7PrimaryKey
|
|
33
|
+
extend ActiveSupport::Concern
|
|
34
|
+
|
|
35
|
+
included do
|
|
36
|
+
before_create :assign_uuidv7_if_needed, if: -> { id.nil? }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def assign_uuidv7_if_needed
|
|
42
|
+
# Skip if id was already set (manual set, bulk insert with ids, etc)
|
|
43
|
+
return if id.present?
|
|
44
|
+
|
|
45
|
+
uuid = SecureRandom.uuid_v7
|
|
46
|
+
RailsUuidPk.log(:debug, "Assigned UUIDv7 #{uuid} to #{self.class.name}")
|
|
47
|
+
self.id = uuid
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Responsibilities**:
|
|
54
|
+
- UUIDv7 generation logic
|
|
55
|
+
- Defensive programming (only assign if id is nil)
|
|
56
|
+
- Hook into ActiveRecord lifecycle
|
|
57
|
+
|
|
58
|
+
#### Migration Helpers (`lib/rails_uuid_pk/migration_helpers.rb`)
|
|
59
|
+
**Responsibilities**:
|
|
60
|
+
- Automatic foreign key type detection
|
|
61
|
+
- Polymorphic association support
|
|
62
|
+
- Schema inspection and caching
|
|
63
|
+
|
|
64
|
+
#### Custom Type (`lib/rails_uuid_pk/type.rb`)
|
|
65
|
+
**Responsibilities**:
|
|
66
|
+
- UUID validation and casting
|
|
67
|
+
- Schema dumping compatibility
|
|
68
|
+
- Rails version awareness
|
|
69
|
+
|
|
70
|
+
#### Railtie (`lib/rails_uuid_pk/railtie.rb`)
|
|
71
|
+
**Responsibilities**:
|
|
72
|
+
- Automatic gem integration
|
|
73
|
+
- Database adapter extensions
|
|
74
|
+
- Generator configuration
|
|
75
|
+
|
|
76
|
+
#### Logging Framework (`lib/rails_uuid_pk.rb`)
|
|
77
|
+
**Responsibilities**:
|
|
78
|
+
- Structured logging infrastructure
|
|
79
|
+
- Rails logger integration with fallback
|
|
80
|
+
- Debug logging for UUID assignment, migration helpers, and adapter registration
|
|
81
|
+
- Production observability support
|
|
82
|
+
- Structured logging with `[RailsUuidPk]` prefix for easy filtering
|
|
83
|
+
- Compatible with existing Rails logging and monitoring systems (Datadog, CloudWatch, etc.)
|
|
84
|
+
|
|
85
|
+
### 2. Database Adapter Extensions
|
|
86
|
+
|
|
87
|
+
#### PostgreSQL (Native Support)
|
|
88
|
+
- Uses PostgreSQL's native UUID type support (16 bytes)
|
|
89
|
+
- No adapter extension needed - Rails handles UUID types natively
|
|
90
|
+
- Full database function compatibility
|
|
91
|
+
- Optimized for PostgreSQL 18's enhanced UUID handling
|
|
92
|
+
|
|
93
|
+
#### Shared UUID Adapter Extension
|
|
94
|
+
```ruby
|
|
95
|
+
# lib/rails_uuid_pk/uuid_adapter_extension.rb
|
|
96
|
+
module RailsUuidPk
|
|
97
|
+
module UuidAdapterExtension
|
|
98
|
+
# Common UUID type support methods shared by MySQL and SQLite adapters
|
|
99
|
+
def native_database_types
|
|
100
|
+
super.merge(uuid: { name: "varchar", limit: 36 })
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def valid_type?(type)
|
|
104
|
+
return true if type == :uuid
|
|
105
|
+
super
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def register_uuid_types(m = type_map)
|
|
109
|
+
RailsUuidPk.log(:debug, "Registering UUID types on #{m.class}")
|
|
110
|
+
m.register_type(/varchar\(36\)/i) { RailsUuidPk::Type::Uuid.new }
|
|
111
|
+
m.register_type("uuid") { RailsUuidPk::Type::Uuid.new }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def initialize_type_map(m = type_map)
|
|
115
|
+
super
|
|
116
|
+
register_uuid_types(m)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def configure_connection
|
|
120
|
+
super
|
|
121
|
+
register_uuid_types
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def type_to_dump(column)
|
|
125
|
+
if column.type == :uuid
|
|
126
|
+
return [ :uuid, {} ]
|
|
127
|
+
end
|
|
128
|
+
super
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### MySQL (`mysql2` gem integration)
|
|
135
|
+
```ruby
|
|
136
|
+
# lib/rails_uuid_pk/mysql2_adapter_extension.rb
|
|
137
|
+
module RailsUuidPk
|
|
138
|
+
module Mysql2AdapterExtension
|
|
139
|
+
include UuidAdapterExtension
|
|
140
|
+
|
|
141
|
+
# MySQL-specific connection configuration
|
|
142
|
+
def configure_connection
|
|
143
|
+
super # Standard UUID type registration
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### SQLite (`sqlite3` gem integration)
|
|
150
|
+
```ruby
|
|
151
|
+
# lib/rails_uuid_pk/sqlite3_adapter_extension.rb
|
|
152
|
+
module RailsUuidPk
|
|
153
|
+
module Sqlite3AdapterExtension
|
|
154
|
+
include UuidAdapterExtension
|
|
155
|
+
|
|
156
|
+
# SQLite-specific connection configuration with transaction awareness
|
|
157
|
+
def configure_connection
|
|
158
|
+
# Only call super if not inside a transaction, as PRAGMA statements
|
|
159
|
+
# cannot be executed inside transactions in SQLite
|
|
160
|
+
super unless open_transactions > 0
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Key Architectural Decisions
|
|
167
|
+
|
|
168
|
+
### Decision 1: App-Level vs Database-Level Generation
|
|
169
|
+
|
|
170
|
+
#### Chosen Approach: App-Level Generation
|
|
171
|
+
```ruby
|
|
172
|
+
# In application code
|
|
173
|
+
before_create :assign_uuidv7_if_needed, if: -> { id.nil? }
|
|
174
|
+
|
|
175
|
+
def assign_uuidv7_if_needed
|
|
176
|
+
return if id.present?
|
|
177
|
+
self.id = SecureRandom.uuid_v7
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### Alternative Considered: Database-Level Generation
|
|
182
|
+
```sql
|
|
183
|
+
-- PostgreSQL native approach
|
|
184
|
+
CREATE TABLE users (
|
|
185
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
|
|
186
|
+
name VARCHAR(255)
|
|
187
|
+
);
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Trade-off Analysis
|
|
191
|
+
|
|
192
|
+
| Aspect | App-Level | Database-Level |
|
|
193
|
+
|--------|-----------|----------------|
|
|
194
|
+
| **Compatibility** | Universal (all DBs) | DB-specific functions required |
|
|
195
|
+
| **Performance** | Minimal overhead | Potentially faster (no app round-trip) |
|
|
196
|
+
| **Testing** | Consistent across environments | Requires DB function setup |
|
|
197
|
+
| **Migration** | App manages UUIDs | DB manages UUIDs |
|
|
198
|
+
| **Dependencies** | Ruby SecureRandom only | Database extensions/functions |
|
|
199
|
+
| **Consistency** | Guaranteed across transactions | Dependent on DB implementation |
|
|
200
|
+
|
|
201
|
+
#### Rationale for App-Level Choice
|
|
202
|
+
1. **Universal Compatibility**: Works with any database without extensions
|
|
203
|
+
2. **Testing Simplicity**: Same behavior in development, test, and production
|
|
204
|
+
3. **Migration Safety**: UUIDs are assigned before database constraints
|
|
205
|
+
4. **Zero Dependencies**: No database-specific setup or extensions required
|
|
206
|
+
5. **Performance**: `SecureRandom.uuid_v7` is sufficiently fast for most applications
|
|
207
|
+
|
|
208
|
+
#### Bulk Operations Limitation
|
|
209
|
+
The app-level callback approach has one important limitation: bulk operations bypass ActiveRecord callbacks. This means operations like `Model.import` or `Model.insert_all` won't automatically generate UUIDs. Applications requiring bulk imports must explicitly assign UUIDs:
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
# Manual UUID assignment required for bulk operations
|
|
213
|
+
users = [{ name: "Alice", id: SecureRandom.uuid_v7 }, { name: "Bob", id: SecureRandom.uuid_v7 }]
|
|
214
|
+
User.insert_all(users) # Bypasses callbacks, requires explicit IDs
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
This is a conscious trade-off for universal compatibility and zero-configuration benefits.
|
|
218
|
+
|
|
219
|
+
### Decision 2: UUIDv7 vs Other UUID Versions
|
|
220
|
+
|
|
221
|
+
#### Chosen: UUIDv7 (RFC 9562)
|
|
222
|
+
- **Monotonic Ordering**: Time-based ordering reduces index fragmentation
|
|
223
|
+
- **Cryptographically Secure**: Uses system CSPRNG for randomness
|
|
224
|
+
- **Standard Compliant**: Latest UUID standard with time-based ordering
|
|
225
|
+
- **Database Friendly**: Better index locality than random UUIDs
|
|
226
|
+
|
|
227
|
+
#### Alternatives Considered
|
|
228
|
+
- **UUIDv4**: Random, maximum unpredictability, poor index performance
|
|
229
|
+
- **UUIDv1**: MAC address based, privacy concerns, time-ordered
|
|
230
|
+
- **Sequential IDs**: Auto-increment, predictable, good performance but security issues
|
|
231
|
+
|
|
232
|
+
#### UUIDv7 Advantages for Rails Applications
|
|
233
|
+
1. **Index Performance**: Monotonic ordering improves B-tree efficiency
|
|
234
|
+
2. **Audit Capabilities**: Time-based ordering enables temporal queries
|
|
235
|
+
3. **Security**: Cryptographically secure generation
|
|
236
|
+
4. **Standards Compliance**: RFC 9562 adherence
|
|
237
|
+
|
|
238
|
+
### Decision 3: Railtie-Based Integration
|
|
239
|
+
|
|
240
|
+
#### Chosen: Automatic Railtie Integration
|
|
241
|
+
```ruby
|
|
242
|
+
# lib/rails_uuid_pk/railtie.rb
|
|
243
|
+
module RailsUuidPk
|
|
244
|
+
class Railtie < ::Rails::Railtie
|
|
245
|
+
initializer "rails_uuid_pk.configure" do
|
|
246
|
+
ActiveSupport.on_load(:active_record) do
|
|
247
|
+
ActiveRecord::Base.include HasUuidv7PrimaryKey
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Benefits
|
|
255
|
+
- **Zero Configuration**: Works immediately after bundle install
|
|
256
|
+
- **Convention over Configuration**: Follows Rails patterns
|
|
257
|
+
- **Backwards Compatible**: Doesn't break existing applications
|
|
258
|
+
- **Automatic**: No manual model changes required
|
|
259
|
+
|
|
260
|
+
#### Alternative: Explicit Inclusion
|
|
261
|
+
```ruby
|
|
262
|
+
# Would require developers to add to each model
|
|
263
|
+
class User < ApplicationRecord
|
|
264
|
+
include RailsUuidPk::HasUuidv7PrimaryKey
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Decision 4: Database Adapter Extension Pattern
|
|
269
|
+
|
|
270
|
+
#### Chosen: Selective Adapter Extensions
|
|
271
|
+
**PostgreSQL**: No extension needed - uses Rails' native UUID type support
|
|
272
|
+
**MySQL & SQLite**: Custom extensions for VARCHAR(36) UUID storage
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
# MySQL extension - provides VARCHAR(36) type mapping
|
|
276
|
+
# lib/rails_uuid_pk/mysql2_adapter_extension.rb
|
|
277
|
+
module RailsUuidPk
|
|
278
|
+
module Mysql2AdapterExtension
|
|
279
|
+
def native_database_types
|
|
280
|
+
super.merge(uuid: { name: "varchar", limit: 36 })
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def register_uuid_types(m = type_map)
|
|
284
|
+
m.register_type(/varchar\(36\)/i) { RailsUuidPk::Type::Uuid.new }
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# SQLite extension - provides VARCHAR(36) type mapping
|
|
290
|
+
# lib/rails_uuid_pk/sqlite3_adapter_extension.rb
|
|
291
|
+
module RailsUuidPk
|
|
292
|
+
module Sqlite3AdapterExtension
|
|
293
|
+
def native_database_types
|
|
294
|
+
super.merge(uuid: { name: "varchar", limit: 36 })
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def register_uuid_types(m = type_map)
|
|
298
|
+
m.register_type(/varchar\(36\)/i) { RailsUuidPk::Type::Uuid.new }
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Benefits
|
|
305
|
+
- **Database-Specific Optimization**: Each database gets appropriate handling
|
|
306
|
+
- **Clean Separation**: Adapter concerns are isolated
|
|
307
|
+
- **Extensible**: Easy to add new database support
|
|
308
|
+
- **Testable**: Each adapter can be tested independently
|
|
309
|
+
|
|
310
|
+
### Decision 5: Opt-out Mechanism for Selective UUID Generation
|
|
311
|
+
|
|
312
|
+
#### Chosen: Class-Level Opt-out with Migration Helper Integration
|
|
313
|
+
```ruby
|
|
314
|
+
# lib/rails_uuid_pk/concern.rb
|
|
315
|
+
module RailsUuidPk
|
|
316
|
+
module HasUuidv7PrimaryKey
|
|
317
|
+
extend ActiveSupport::Concern
|
|
318
|
+
|
|
319
|
+
module ClassMethods
|
|
320
|
+
def use_integer_primary_key
|
|
321
|
+
@uses_integer_primary_key = true
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def uses_uuid_primary_key?
|
|
325
|
+
!@uses_integer_primary_key
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
included do
|
|
330
|
+
before_create :assign_uuidv7_if_needed, if: -> { id.nil? && self.class.uses_uuid_primary_key? }
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
#### Migration Helper Integration
|
|
337
|
+
```ruby
|
|
338
|
+
# lib/rails_uuid_pk/migration_helpers.rb
|
|
339
|
+
def references(*args, **options)
|
|
340
|
+
ref_name = args.first
|
|
341
|
+
ref_table = options.delete(:to_table) || ref_name.to_s.pluralize
|
|
342
|
+
|
|
343
|
+
# Automatic type detection for mixed PK scenarios
|
|
344
|
+
if uuid_primary_key?(ref_table)
|
|
345
|
+
options[:type] = :uuid
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
super(*args, **options)
|
|
349
|
+
end
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
#### Migration Schema Updates
|
|
353
|
+
When opting out of UUID primary keys, developers must also modify the generated migration:
|
|
354
|
+
|
|
355
|
+
```ruby
|
|
356
|
+
# Generated migration (modify this line)
|
|
357
|
+
create_table :legacy_models, id: :uuid do |t| # Change :uuid to :integer
|
|
358
|
+
t.string :name
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# After modification
|
|
362
|
+
create_table :legacy_models, id: :integer do |t| # Now uses integer primary keys
|
|
363
|
+
t.string :name
|
|
364
|
+
end
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### Benefits
|
|
368
|
+
- **Selective Control**: Individual models can opt out while maintaining overall UUID usage
|
|
369
|
+
- **Migration Intelligence**: Automatic foreign key type detection for mixed scenarios
|
|
370
|
+
- **Zero Breaking Changes**: Existing applications continue to work unchanged
|
|
371
|
+
- **Clean API**: Simple declarative syntax for opting out
|
|
372
|
+
- **Inheritance Aware**: Subclasses must explicitly opt out (no automatic inheritance)
|
|
373
|
+
|
|
374
|
+
#### Trade-offs
|
|
375
|
+
- **Inheritance Behavior**: Opt-out is not inherited - each model must explicitly declare preference
|
|
376
|
+
- **Migration Complexity**: Foreign key type detection becomes more complex in mixed environments
|
|
377
|
+
- **Testing Requirements**: Mixed scenarios require additional test coverage
|
|
378
|
+
|
|
379
|
+
## Migration Performance Implications
|
|
380
|
+
|
|
381
|
+
### Schema Inspection Caching
|
|
382
|
+
```ruby
|
|
383
|
+
# lib/rails_uuid_pk/migration_helpers.rb
|
|
384
|
+
def uuid_primary_key?(table_name)
|
|
385
|
+
@uuid_pk_cache ||= {}
|
|
386
|
+
return @uuid_pk_cache[table_name] if @uuid_pk_cache.key?(table_name)
|
|
387
|
+
|
|
388
|
+
# Database schema inspection
|
|
389
|
+
conn = connection
|
|
390
|
+
pk_column = find_primary_key_column(table_name, conn)
|
|
391
|
+
@uuid_pk_cache[table_name] = !!(pk_column && pk_column.sql_type =~ /uuid|varchar\(36\)/)
|
|
392
|
+
end
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Performance Impact**:
|
|
396
|
+
- **Caching**: Reduces database queries during migrations
|
|
397
|
+
- **Memory**: Small memory footprint for cache
|
|
398
|
+
- **Accuracy**: Cache is per-migration instance, ensuring freshness
|
|
399
|
+
|
|
400
|
+
### Foreign Key Type Detection
|
|
401
|
+
```ruby
|
|
402
|
+
def references(*args, **options)
|
|
403
|
+
ref_name = args.first
|
|
404
|
+
ref_table = options.delete(:to_table) || ref_name.to_s.pluralize
|
|
405
|
+
|
|
406
|
+
# Automatic type detection
|
|
407
|
+
if uuid_primary_key?(ref_table)
|
|
408
|
+
options[:type] = :uuid
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
super(*args, **options)
|
|
412
|
+
end
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Benefits**:
|
|
416
|
+
- **Automatic**: No manual foreign key type specification
|
|
417
|
+
- **Correct**: Ensures type consistency across relationships
|
|
418
|
+
- **Polymorphic**: Handles complex association scenarios
|
|
419
|
+
|
|
420
|
+
## Database Replication and Backup Considerations
|
|
421
|
+
|
|
422
|
+
### Replication Compatibility
|
|
423
|
+
- **UUID Generation**: App-level generation is replication-safe
|
|
424
|
+
- **No Conflicts**: UUIDs are globally unique by design
|
|
425
|
+
- **Ordering**: Time-based UUIDs maintain logical ordering across replicas
|
|
426
|
+
|
|
427
|
+
### Backup and Restore
|
|
428
|
+
- **Data Integrity**: UUIDs remain valid across backup/restore cycles
|
|
429
|
+
- **References**: Foreign key relationships preserved
|
|
430
|
+
- **Consistency**: No auto-increment conflicts or renumbering issues
|
|
431
|
+
|
|
432
|
+
### Multi-Region Deployments
|
|
433
|
+
- **Global Uniqueness**: UUIDs work across geographically distributed systems
|
|
434
|
+
- **Conflict Resolution**: No primary key conflicts in multi-master setups
|
|
435
|
+
- **Audit Trails**: Time-based UUIDs enable global event ordering
|
|
436
|
+
|
|
437
|
+
## ORM and Query Builder Impact
|
|
438
|
+
|
|
439
|
+
### ActiveRecord Integration
|
|
440
|
+
- **Seamless**: Works with all ActiveRecord features
|
|
441
|
+
- **Associations**: Supports belongs_to, has_many, has_one relationships
|
|
442
|
+
- **Validations**: Compatible with all Rails validations
|
|
443
|
+
- **Callbacks**: Integrates with Rails callback system
|
|
444
|
+
|
|
445
|
+
### Query Performance
|
|
446
|
+
- **Index Usage**: Leverages database indexes effectively
|
|
447
|
+
- **Range Queries**: UUIDv7 enables efficient time-based queries
|
|
448
|
+
- **Join Performance**: Foreign key relationships perform well
|
|
449
|
+
- **Batch Operations**: Compatible with find_each, find_in_batches
|
|
450
|
+
|
|
451
|
+
### Development Experience
|
|
452
|
+
- **No Code Changes**: Existing Rails code works unchanged
|
|
453
|
+
- **Migration Helpers**: Automatic foreign key type detection
|
|
454
|
+
- **Schema Dumping**: Compatible with Rails schema tools
|
|
455
|
+
- **Testing**: Works with all Rails testing frameworks
|
|
456
|
+
|
|
457
|
+
## Error Handling and Resilience
|
|
458
|
+
|
|
459
|
+
### Database Connection Failures
|
|
460
|
+
```ruby
|
|
461
|
+
def uuid_primary_key?(table_name)
|
|
462
|
+
conn = connection
|
|
463
|
+
return false unless conn.respond_to?(:table_exists?) && conn.table_exists?(table_name)
|
|
464
|
+
|
|
465
|
+
# Graceful fallback on errors
|
|
466
|
+
pk_column = find_primary_key_column(table_name, conn)
|
|
467
|
+
!!(pk_column && pk_column.sql_type =~ /uuid|varchar\(36\)/)
|
|
468
|
+
rescue => e
|
|
469
|
+
# Log warning and return false on connection errors
|
|
470
|
+
Rails.logger.warn "rails-uuid-pk: Could not check table #{table_name}: #{e.message}"
|
|
471
|
+
false
|
|
472
|
+
end
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Schema Inconsistencies
|
|
476
|
+
- **Defensive Programming**: Handles missing tables gracefully
|
|
477
|
+
- **Type Safety**: Validates UUID format before database operations
|
|
478
|
+
- **Migration Safety**: Works during schema changes and rollbacks
|
|
479
|
+
|
|
480
|
+
## Future Evolution Considerations
|
|
481
|
+
|
|
482
|
+
### Extensibility Points
|
|
483
|
+
- **Configuration System**: Planned configuration options for customization
|
|
484
|
+
- **Plugin Architecture**: Extensible adapter system for new databases
|
|
485
|
+
- **Performance Monitoring**: Optional telemetry and metrics collection
|
|
486
|
+
|
|
487
|
+
### Migration Path
|
|
488
|
+
- **Backwards Compatibility**: Current design supports gradual migration
|
|
489
|
+
- **Version Pinning**: Semantic versioning for breaking changes
|
|
490
|
+
- **Deprecation Warnings**: Clear communication of deprecated features
|
|
491
|
+
|
|
492
|
+
### Performance Optimizations
|
|
493
|
+
- **Native Database Functions**: Future option for database-level generation
|
|
494
|
+
- **Bulk Operations**: Optimized handling for large data imports
|
|
495
|
+
- **Index Strategies**: Advanced indexing recommendations
|
|
496
|
+
|
|
497
|
+
## Conclusion
|
|
498
|
+
|
|
499
|
+
The rails-uuid-pk architecture prioritizes **compatibility**, **simplicity**, and **performance** while maintaining **security** and **reliability**. Key decisions like app-level UUID generation and Railtie-based integration enable the zero-configuration experience while ensuring robust operation across diverse Rails applications and database environments.
|
|
500
|
+
|
|
501
|
+
The architecture successfully balances competing concerns:
|
|
502
|
+
- **Developer Experience**: Zero-configuration adoption
|
|
503
|
+
- **Database Compatibility**: Works across PostgreSQL, MySQL, and SQLite
|
|
504
|
+
- **Performance**: UUIDv7 provides better characteristics than alternatives
|
|
505
|
+
- **Maintainability**: Clean, modular design with clear separation of concerns
|
|
506
|
+
- **Future-Proofing**: Extensible architecture for ongoing evolution
|
|
507
|
+
|
|
508
|
+
This architectural foundation enables rails-uuid-pk to serve as a reliable, production-ready solution for UUID primary keys in Rails applications.
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,84 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v1.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.13.0] - 2026-01-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **`rails_uuid_pk:add_opt_outs` Rails Generator**: New generator to automatically add `use_integer_primary_key` to models with integer primary keys in existing applications
|
|
12
|
+
- Scans all ActiveRecord models in `app/models/**/*.rb`
|
|
13
|
+
- Inspects database schema to detect integer primary keys
|
|
14
|
+
- Adds `use_integer_primary_key` calls to eligible models
|
|
15
|
+
- Command-line options: `--dry-run` (preview changes), `--verbose` (detailed output)
|
|
16
|
+
- Idempotent operation - safe to run multiple times
|
|
17
|
+
- Comprehensive error handling and user feedback
|
|
18
|
+
- **Trilogy Adapter Support**: Added support for the trilogy gem as an alternative to mysql2 for MySQL connections
|
|
19
|
+
- New `TrilogyAdapterExtension` module for trilogy adapter integration
|
|
20
|
+
- Automatic detection and registration of trilogy adapter for UUID type support
|
|
21
|
+
- Comprehensive test suite for trilogy adapter functionality
|
|
22
|
+
- Updated documentation to reflect trilogy compatibility alongside mysql2
|
|
23
|
+
- Added trilogy as development dependency for testing
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **Documentation Consistency**: Ensured all documentation files consistently communicate that UUIDv7 is the default behavior, while opt-out functionality exists for exceptional cases (legacy tables, third-party integrations, etc.)
|
|
27
|
+
|
|
28
|
+
### Documentation
|
|
29
|
+
- **Clarified Default UUIDv7 Behavior**: Updated all documentation files to explicitly state that rails-uuid-pk assumes UUIDv7 primary keys by default for all models, with `use_integer_primary_key` being an exception for models requiring integer primary keys
|
|
30
|
+
- **README.md**: Emphasized "Assumes UUIDv7 primary keys by default" and clarified opt-out as "Exception: Opting Out"
|
|
31
|
+
- **ARCHITECTURE.md**: Updated "Zero-Configuration Philosophy" to highlight automatic UUIDv7 primary keys by default
|
|
32
|
+
- **DEVELOPMENT.md**: Clarified opt-out functionality as "exceptions only" for specific use cases
|
|
33
|
+
- **AGENTS.md**: Updated project overview to state "automatically assumes UUIDv7 primary keys by default... Models requiring integer primary keys are treated as exceptions"
|
|
34
|
+
- **Bulk Operations Documentation**: Added comprehensive documentation about bulk operations performance and limitations across README.md and PERFORMANCE.md, explaining that UUIDs are not automatically generated during bulk insert operations (`insert_all`, `upsert_all`, `Model.import`) and require manual UUID assignment using `SecureRandom.uuid_v7`, while highlighting the significant performance benefits when properly implemented
|
|
35
|
+
|
|
36
|
+
### Technical Details
|
|
37
|
+
- Added `lib/generators/rails_uuid_pk/add_opt_outs_generator.rb` with full Rails generator implementation
|
|
38
|
+
- Added `lib/generators/rails_uuid_pk/templates/.keep` for directory structure maintenance
|
|
39
|
+
- Added `test/generators/add_opt_outs_generator_test.rb` with comprehensive test suite (9 tests, 18 assertions)
|
|
40
|
+
- Added `test/railtie_test.rb` with dedicated railtie functionality tests
|
|
41
|
+
- Updated `lib/rails_uuid_pk.rb` to load generator components
|
|
42
|
+
- Generator includes smart model file parsing, database schema inspection, and safe file modification
|
|
43
|
+
- Full cross-database compatibility (PostgreSQL, MySQL, SQLite)
|
|
44
|
+
- Production-ready with proper error handling and logging integration
|
|
45
|
+
- Added `lib/rails_uuid_pk/trilogy_adapter_extension.rb` with trilogy-specific adapter extension
|
|
46
|
+
- Updated `lib/rails_uuid_pk/railtie.rb` to include trilogy adapter hooks and type registration
|
|
47
|
+
- Updated `lib/rails_uuid_pk.rb` to load trilogy adapter extension
|
|
48
|
+
- Added `test/database_adapters/trilogy_test.rb` with comprehensive trilogy adapter tests
|
|
49
|
+
- Added trilogy database configuration to test suite
|
|
50
|
+
- Updated gemspec with trilogy development dependency
|
|
51
|
+
- Enhanced README.md and gemspec descriptions to mention trilogy support
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
- **Migration Helpers Robustness**: Enhanced primary key detection with improved error handling and safety checks
|
|
55
|
+
- Added safe navigation operators (`&.`) for `sql_type` access to prevent `NoMethodError` on nil objects
|
|
56
|
+
- Added comprehensive error handling with rescue blocks for database connection failures
|
|
57
|
+
- Restricted UUID primary key detection to only consider standard Rails primary keys named 'id'
|
|
58
|
+
- Graceful fallback to integer foreign keys when database operations fail
|
|
59
|
+
|
|
60
|
+
### Testing
|
|
61
|
+
- **Comprehensive Test Suite Expansion**: Added extensive test coverage for edge cases and error scenarios
|
|
62
|
+
- Added `test/railtie_test.rb` with 1 test covering railtie connection registration logic
|
|
63
|
+
- Expanded `test/configuration/setup_test.rb` with 5 additional tests (12 total) covering railtie UUID type registration, database configurations, migration helpers inclusion, adapter extension registration, and schema format configuration
|
|
64
|
+
- Enhanced `test/database_adapters/mysql_test.rb` with 1 additional test for MySQL adapter extension `configure_connection` behavior
|
|
65
|
+
- Enhanced `test/database_adapters/sqlite_test.rb` with 1 additional test for SQLite adapter extension transaction-aware configuration
|
|
66
|
+
- Enhanced `test/database_adapters/uuid_adapter_extension_test.rb` with 2 additional tests covering `type_to_dump` functionality and migration helpers edge cases
|
|
67
|
+
- Expanded `test/migration_helpers/references_test.rb` with 6 additional tests (11 total) covering polymorphic associations with mixed primary key types, nil options handling, empty polymorphic options, column lookup error handling, method chaining safety, and rescue block behavior
|
|
68
|
+
- Total test expansion: 16 new tests across 6 test files, improving robustness and edge case coverage
|
|
69
|
+
|
|
70
|
+
## [0.12.0] - 2026-01-17
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
- **Refactored Database Adapter Extensions**: Eliminated 152 lines of duplicated code (97% similarity) between MySQL and SQLite adapter extensions by introducing a shared `UuidAdapterExtension` module
|
|
74
|
+
- Created `lib/rails_uuid_pk/uuid_adapter_extension.rb` with common UUID type support functionality
|
|
75
|
+
- Refactored `Mysql2AdapterExtension` and `Sqlite3AdapterExtension` to include the shared module
|
|
76
|
+
- Maintained SQLite-specific transaction-aware connection configuration while standardizing all other UUID handling logic
|
|
77
|
+
- Improved code maintainability and reduced duplication from 156 lines to 99 lines total (37% reduction)
|
|
78
|
+
|
|
79
|
+
### Technical Details
|
|
80
|
+
- Added shared `UuidAdapterExtension` module containing `native_database_types`, `valid_type?`, `register_uuid_types`, `initialize_type_map`, `configure_connection`, and `type_to_dump` methods
|
|
81
|
+
- Updated `Mysql2AdapterExtension` to include shared module with minimal override for `configure_connection`
|
|
82
|
+
- Updated `Sqlite3AdapterExtension` to include shared module while preserving transaction-aware `configure_connection` implementation
|
|
83
|
+
- Added require statement for new shared module in main library file
|
|
84
|
+
- All existing functionality preserved with identical test suite results (119 tests, 0 failures, 0 errors)
|
|
85
|
+
|
|
8
86
|
## [0.11.0] - 2026-01-16
|
|
9
87
|
|
|
10
88
|
### Added
|