custom_id 0.1.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 +7 -0
- data/AGENTS.md +143 -0
- data/CHANGELOG.md +20 -0
- data/CLAUDE.md +133 -0
- data/LICENSE.txt +21 -0
- data/README.md +240 -0
- data/lib/custom_id/concern.rb +103 -0
- data/lib/custom_id/db_extension.rb +384 -0
- data/lib/custom_id/installer.rb +55 -0
- data/lib/custom_id/railtie.rb +16 -0
- data/lib/custom_id/version.rb +5 -0
- data/lib/custom_id.rb +12 -0
- data/lib/tasks/custom_id.rake +118 -0
- data/llms/overview.md +274 -0
- data/llms/usage.md +530 -0
- metadata +122 -0
data/llms/usage.md
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# CustomId – Usage Patterns for LLMs
|
|
2
|
+
|
|
3
|
+
> Concrete, copy-pasteable examples covering every supported scenario.
|
|
4
|
+
> Optimised for LLM code generation – each section is self-contained.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Gemfile
|
|
12
|
+
gem "custom_id"
|
|
13
|
+
|
|
14
|
+
# Terminal
|
|
15
|
+
bundle install
|
|
16
|
+
rails custom_id:install # creates config/initializers/custom_id.rb
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The initializer auto-includes `CustomId::Concern` into every `ActiveRecord::Base`
|
|
20
|
+
subclass. No `include` is needed in individual models.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. Migration – string primary key
|
|
25
|
+
|
|
26
|
+
Always declare the id column as `:string` for tables where `cid` manages the
|
|
27
|
+
primary key.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
class CreateUsers < ActiveRecord::Migration[7.1]
|
|
31
|
+
def change
|
|
32
|
+
create_table :users, id: :string do |t|
|
|
33
|
+
t.string :name, null: false
|
|
34
|
+
t.string :email, null: false, index: { unique: true }
|
|
35
|
+
t.timestamps
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Do not** set a `default:` on the id column – the gem handles that.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. Basic model – default primary key
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
class User < ApplicationRecord
|
|
49
|
+
cid "usr"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Result:
|
|
53
|
+
user = User.create!(name: "Alice", email: "alice@example.com")
|
|
54
|
+
user.id # => "usr_7xKmN2pQaBcDeFgH" (16 random Base58 chars after "usr_")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The ID format is always `"#{prefix}_#{random}"` where `random` is `size`
|
|
58
|
+
Base58 characters long.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 4. Custom size
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
class ApiKey < ApplicationRecord
|
|
66
|
+
cid "key", size: 32
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
ApiKey.create!.id # => "key_<32 Base58 chars>"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`size` controls the length of the random portion only, not the total ID length.
|
|
73
|
+
Total length = `prefix.length + 1 + size`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 5. Non-primary-key column (`name:`)
|
|
78
|
+
|
|
79
|
+
Use when you want a generated reference code in a non-PK column. The primary
|
|
80
|
+
key can remain an integer or UUID.
|
|
81
|
+
|
|
82
|
+
**Migration:**
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
class AddSlugToArticles < ActiveRecord::Migration[7.1]
|
|
86
|
+
def change
|
|
87
|
+
add_column :articles, :slug, :string
|
|
88
|
+
add_index :articles, :slug, unique: true
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Model:**
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
class Article < ApplicationRecord
|
|
97
|
+
# id is a regular integer PK managed by the DB
|
|
98
|
+
cid "art", name: :slug, size: 12
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
article = Article.create!(title: "Hello World")
|
|
102
|
+
article.id # => 1 (integer)
|
|
103
|
+
article.slug # => "art_aBcDeFgHiJkL"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Skipping generation** – pre-set the attribute before save:
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
Article.create!(title: "Custom slug", slug: "art_my_special_code")
|
|
110
|
+
# slug will NOT be overwritten because it is not nil
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## 6. Embedding parent ID characters (`related:`)
|
|
116
|
+
|
|
117
|
+
Visually embeds the first N characters of a parent model's random portion into
|
|
118
|
+
the child's ID. Useful for traceability – you can tell which workspace a
|
|
119
|
+
document belongs to just by looking at its ID.
|
|
120
|
+
|
|
121
|
+
**Models:**
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
class Workspace < ApplicationRecord
|
|
125
|
+
cid "wsp"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
class Document < ApplicationRecord
|
|
129
|
+
belongs_to :workspace
|
|
130
|
+
# Borrow first 6 chars of workspace's random portion; total random = 22
|
|
131
|
+
cid "doc", size: 22, related: { workspace: 6 }
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Migration for Document:**
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
create_table :documents, id: :string do |t|
|
|
139
|
+
t.string :title, null: false
|
|
140
|
+
t.string :workspace_id, null: false
|
|
141
|
+
t.timestamps
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Usage:**
|
|
146
|
+
|
|
147
|
+
```ruby
|
|
148
|
+
workspace = Workspace.create!(name: "Acme")
|
|
149
|
+
workspace.id # => "wsp_ABCDEF..."
|
|
150
|
+
|
|
151
|
+
doc = Document.create!(title: "Spec", workspace: workspace)
|
|
152
|
+
doc.id # => "doc_ABCDEF<16 random chars>"
|
|
153
|
+
# ^^^^^^ first 6 chars of workspace's random portion
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Constraint:** `size` must be strictly greater than the borrowed char count.
|
|
157
|
+
`cid "doc", size: 6, related: { workspace: 6 }` raises `ArgumentError`.
|
|
158
|
+
|
|
159
|
+
**Nil parent:** if the foreign key is `nil` at create time, the shared portion
|
|
160
|
+
falls back to `""` and the full `size` is random.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 7. `related:` key must match the `belongs_to` name
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
belongs_to :user → related: { user: 8 } ✓
|
|
168
|
+
belongs_to :created_by_user → related: { created_by_user: 8 } ✓
|
|
169
|
+
belongs_to :user, foreign_key: :author_id → related: { user: 8 } ✓
|
|
170
|
+
# gem resolves FK via reflection
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 8. Multiple `cid` calls on one model
|
|
176
|
+
|
|
177
|
+
Each call registers an independent `before_create` callback. Use this to
|
|
178
|
+
manage multiple string columns:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
class Contract < ApplicationRecord
|
|
182
|
+
cid "ctr" # manages :id
|
|
183
|
+
cid "ref", name: :ref_number, size: 8 # manages :ref_number column
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Warning:** calling `cid` twice for the **same column** on the same class
|
|
188
|
+
stacks two callbacks; the second one will always skip because the first already
|
|
189
|
+
set the value. Don't do this.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 9. Manual include (without the Rails initializer)
|
|
194
|
+
|
|
195
|
+
For non-Rails setups or for a single model:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
require "custom_id"
|
|
199
|
+
|
|
200
|
+
class MyModel < ActiveRecord::Base
|
|
201
|
+
include CustomId::Concern
|
|
202
|
+
cid "my"
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 10. Database trigger alternative (`DbExtension`)
|
|
209
|
+
|
|
210
|
+
Use when IDs must be generated even for raw SQL inserts (bulk imports, ETL).
|
|
211
|
+
Supports **PostgreSQL 9.6+**, **MySQL 5.7+**, and **SQLite 3.0+**.
|
|
212
|
+
|
|
213
|
+
### 10a. PostgreSQL
|
|
214
|
+
|
|
215
|
+
**Requires the `pgcrypto` extension.**
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Enable pgcrypto via rake task (once per database)
|
|
219
|
+
rails custom_id:db:enable_pgcrypto
|
|
220
|
+
# or for a specific database in a multi-database app:
|
|
221
|
+
rails "custom_id:db:enable_pgcrypto[postgres]"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Or in a migration:
|
|
225
|
+
|
|
226
|
+
```ruby
|
|
227
|
+
class EnablePgcrypto < ActiveRecord::Migration[8.0]
|
|
228
|
+
def up = enable_extension "pgcrypto"
|
|
229
|
+
def down = disable_extension "pgcrypto"
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Install trigger in the same migration as table creation:**
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
class CreateTeams < ActiveRecord::Migration[7.1]
|
|
237
|
+
def up
|
|
238
|
+
create_table :teams, id: :string do |t|
|
|
239
|
+
t.string :name, null: false
|
|
240
|
+
t.timestamps
|
|
241
|
+
end
|
|
242
|
+
CustomId::DbExtension.install_trigger!(connection, :teams, prefix: "tea")
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def down
|
|
246
|
+
CustomId::DbExtension.uninstall_trigger!(connection, :teams)
|
|
247
|
+
drop_table :teams
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
PostgreSQL returns the generated PK via `RETURNING "id"`, so the ActiveRecord
|
|
253
|
+
object has the correct `id` after `create` without any extra work.
|
|
254
|
+
|
|
255
|
+
### 10b. MySQL – always pair with `cid` on the model
|
|
256
|
+
|
|
257
|
+
**⚠ Important:** MySQL's `LAST_INSERT_ID()` returns `0` for
|
|
258
|
+
non-`AUTO_INCREMENT` columns. After an AR `create` that leaves `id` blank,
|
|
259
|
+
Rails reads back `0` even though the DB row has the correct trigger-generated
|
|
260
|
+
value.
|
|
261
|
+
|
|
262
|
+
**Required pattern:** declare `cid` on the model alongside the trigger.
|
|
263
|
+
|
|
264
|
+
```ruby
|
|
265
|
+
# model – cid generates id in Ruby before INSERT
|
|
266
|
+
class Order < ApplicationRecord
|
|
267
|
+
cid "ord"
|
|
268
|
+
end
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
# migration
|
|
273
|
+
class CreateOrders < ActiveRecord::Migration[8.0]
|
|
274
|
+
def up
|
|
275
|
+
create_table :orders, id: :string do |t|
|
|
276
|
+
t.string :status, null: false
|
|
277
|
+
t.timestamps
|
|
278
|
+
end
|
|
279
|
+
CustomId::DbExtension.install_trigger!(connection, :orders, prefix: "ord")
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def down
|
|
283
|
+
CustomId::DbExtension.uninstall_trigger!(connection, :orders)
|
|
284
|
+
drop_table :orders
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
With `cid` on the model, the INSERT includes `id` in the column list, the
|
|
290
|
+
trigger's `IF NEW.id IS NULL` guard is false (no-op), and AR has the correct
|
|
291
|
+
ID. The trigger still fires for raw SQL inserts that bypass ActiveRecord.
|
|
292
|
+
|
|
293
|
+
The rake task `custom_id:db:add_trigger` prints a reminder when targeting MySQL:
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
warn MySQL: pair this trigger with `cid "ord"` on the model.
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 10c. SQLite
|
|
300
|
+
|
|
301
|
+
SQLite uses two strategies automatically selected by `install_trigger!`:
|
|
302
|
+
|
|
303
|
+
**Nullable / non-PK column** → AFTER INSERT trigger updates the row in place.
|
|
304
|
+
|
|
305
|
+
**NOT NULL primary key** → BEFORE INSERT + RAISE(IGNORE) pattern:
|
|
306
|
+
|
|
307
|
+
```ruby
|
|
308
|
+
# migration
|
|
309
|
+
class CreateItems < ActiveRecord::Migration[8.0]
|
|
310
|
+
def up
|
|
311
|
+
create_table :items, id: :string do |t|
|
|
312
|
+
t.string :name
|
|
313
|
+
end
|
|
314
|
+
CustomId::DbExtension.install_trigger!(connection, :items, prefix: "itm")
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def down
|
|
318
|
+
CustomId::DbExtension.uninstall_trigger!(connection, :items)
|
|
319
|
+
drop_table :items
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
⚠ When the BEFORE INSERT + RAISE(IGNORE) path is used, `RETURNING "id"` on
|
|
325
|
+
the outer INSERT returns nothing. The in-memory AR record may have a stale `id`
|
|
326
|
+
until reloaded:
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
item = Item.create!(name: "Widget")
|
|
330
|
+
item.reload # fetch the trigger-generated id from the DB
|
|
331
|
+
item.id # => "itm_Ab3xY7…"
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 10d. Custom column and size (all adapters)
|
|
335
|
+
|
|
336
|
+
```ruby
|
|
337
|
+
CustomId::DbExtension.install_trigger!(
|
|
338
|
+
connection, :reports,
|
|
339
|
+
prefix: "rpt",
|
|
340
|
+
column: :report_key, # default: :id
|
|
341
|
+
size: 24 # default: 16
|
|
342
|
+
)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 10e. Remove trigger (all adapters)
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
CustomId::DbExtension.uninstall_trigger!(connection, :teams)
|
|
349
|
+
CustomId::DbExtension.uninstall_trigger!(connection, :reports, column: :report_key)
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### 10f. Check adapter support
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
CustomId::DbExtension.supported?(ActiveRecord::Base.connection) # => true/false
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 11. Rails installer rake tasks
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
rails custom_id:install # create config/initializers/custom_id.rb
|
|
364
|
+
rails custom_id:uninstall # remove config/initializers/custom_id.rb
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The installed file contains:
|
|
368
|
+
|
|
369
|
+
```ruby
|
|
370
|
+
# frozen_string_literal: true
|
|
371
|
+
ActiveSupport.on_load(:active_record) do
|
|
372
|
+
include CustomId::Concern
|
|
373
|
+
end
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 12. Complete working example
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
# db/migrate/..._create_workspace_documents.rb
|
|
382
|
+
class CreateWorkspaceDocuments < ActiveRecord::Migration[7.1]
|
|
383
|
+
def change
|
|
384
|
+
create_table :workspaces, id: :string do |t|
|
|
385
|
+
t.string :name, null: false
|
|
386
|
+
t.timestamps
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
create_table :documents, id: :string do |t|
|
|
390
|
+
t.string :title, null: false
|
|
391
|
+
t.string :workspace_id, null: false, index: true
|
|
392
|
+
t.string :slug, index: { unique: true }
|
|
393
|
+
t.timestamps
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# app/models/workspace.rb
|
|
399
|
+
class Workspace < ApplicationRecord
|
|
400
|
+
has_many :documents
|
|
401
|
+
cid "wsp"
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# app/models/document.rb
|
|
405
|
+
class Document < ApplicationRecord
|
|
406
|
+
belongs_to :workspace
|
|
407
|
+
cid "doc", size: 24, related: { workspace: 6 }
|
|
408
|
+
cid "dsl", name: :slug, size: 10
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Usage in console / specs
|
|
412
|
+
workspace = Workspace.create!(name: "Acme Corp")
|
|
413
|
+
# workspace.id => "wsp_AbCdEf1234567890"
|
|
414
|
+
|
|
415
|
+
doc = Document.create!(title: "Roadmap", workspace:)
|
|
416
|
+
# doc.id => "doc_AbCdEf<18 random chars>" (shares "AbCdEf" with workspace)
|
|
417
|
+
# doc.slug => "dsl_<10 random chars>"
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## 13. Error reference
|
|
423
|
+
|
|
424
|
+
| Error | Message | Cause | Fix |
|
|
425
|
+
|-------|---------|-------|-----|
|
|
426
|
+
| `ArgumentError` | `size (N) must be greater than the number of shared characters (M)` | `size <= chars_to_borrow` in `related:` | Increase `size` |
|
|
427
|
+
| `NotImplementedError` | `CustomId::DbExtension does not support …` | `DbExtension` called on an unsupported adapter | Supported: PG, MySQL, SQLite |
|
|
428
|
+
| `NotImplementedError` | `The pgcrypto PostgreSQL extension is required but not enabled. Run: rails custom_id:db:enable_pgcrypto` | pgcrypto missing | Run the rake task or add migration |
|
|
429
|
+
| `ActiveRecord::NotNullViolation` | `NOT NULL constraint failed: table.id` | String PK table, `cid` callback not firing | Verify include / table schema |
|
|
430
|
+
| `id = "0"` after `Model.create` (MySQL) | *(no exception)* | `LAST_INSERT_ID()` returns 0 for string PKs | Add `cid "prefix"` to the model |
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 14. DB-extension rake tasks
|
|
435
|
+
|
|
436
|
+
These tasks let you install and remove `CustomId::DbExtension` objects without
|
|
437
|
+
writing migration code. They wrap the `DbExtension` class methods and require
|
|
438
|
+
a live database connection (`:environment` task).
|
|
439
|
+
|
|
440
|
+
All tasks accept an optional `DATABASE` positional argument that targets a
|
|
441
|
+
named database from `database.yml`. Omit it to use the default connection.
|
|
442
|
+
|
|
443
|
+
**Enable pgcrypto (PostgreSQL only – once per database)**
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
rails custom_id:db:enable_pgcrypto
|
|
447
|
+
rails "custom_id:db:enable_pgcrypto[postgres]" # multi-database
|
|
448
|
+
# create pgcrypto extension
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Install the shared Base58 function (PG and MySQL)**
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
rails custom_id:db:install_function
|
|
455
|
+
rails "custom_id:db:install_function[postgres]" # multi-database
|
|
456
|
+
# create custom_id_base58() function
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Safe to call multiple times – uses `CREATE OR REPLACE` / `IF NOT EXISTS`.
|
|
460
|
+
|
|
461
|
+
**Remove the shared Base58 function**
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
rails custom_id:db:uninstall_function
|
|
465
|
+
rails "custom_id:db:uninstall_function[postgres]" # multi-database
|
|
466
|
+
# remove custom_id_base58() function
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Add a BEFORE INSERT trigger to a table**
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Minimal – column defaults to :id, size defaults to 16
|
|
473
|
+
rails "custom_id:db:add_trigger[users,usr]"
|
|
474
|
+
# create trigger on users.id (prefix=usr, size=16)
|
|
475
|
+
|
|
476
|
+
# Custom column and size
|
|
477
|
+
rails "custom_id:db:add_trigger[reports,rpt,report_key,24]"
|
|
478
|
+
# create trigger on reports.report_key (prefix=rpt, size=24)
|
|
479
|
+
|
|
480
|
+
# Multi-database (skip optional args with empty positions)
|
|
481
|
+
rails "custom_id:db:add_trigger[users,usr,,,postgres]"
|
|
482
|
+
rails "custom_id:db:add_trigger[reports,rpt,report_key,24,postgres]"
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Arguments (positional, comma-separated inside brackets):
|
|
486
|
+
|
|
487
|
+
| Position | Name | Default | Notes |
|
|
488
|
+
|----------|------|---------|-------|
|
|
489
|
+
| 1 | `table` | required | Table name |
|
|
490
|
+
| 2 | `prefix` | required | ID prefix string |
|
|
491
|
+
| 3 | `column` | `id` | Column to populate |
|
|
492
|
+
| 4 | `size` | `16` | Random portion length |
|
|
493
|
+
| 5 | `database` | *(default)* | Named DB from `database.yml` |
|
|
494
|
+
|
|
495
|
+
**MySQL warning:** when targeting a MySQL connection the task also prints:
|
|
496
|
+
|
|
497
|
+
```
|
|
498
|
+
warn MySQL: pair this trigger with `cid "prefix"` on the model.
|
|
499
|
+
Without cid, ActiveRecord reads LAST_INSERT_ID() = 0 for string PKs
|
|
500
|
+
and nil for other trigger-managed columns after INSERT.
|
|
501
|
+
The trigger still fires for raw SQL inserts that bypass ActiveRecord.
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**Remove a BEFORE INSERT trigger from a table**
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
rails "custom_id:db:remove_trigger[users]"
|
|
508
|
+
# remove trigger on users.id
|
|
509
|
+
|
|
510
|
+
rails "custom_id:db:remove_trigger[reports,report_key]"
|
|
511
|
+
# remove trigger on reports.report_key
|
|
512
|
+
|
|
513
|
+
rails "custom_id:db:remove_trigger[users,,postgres]" # multi-database
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Error behaviour**
|
|
517
|
+
|
|
518
|
+
If pgcrypto is missing on PostgreSQL:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
error: The pgcrypto PostgreSQL extension is required but not enabled.
|
|
522
|
+
Run: rails custom_id:db:enable_pgcrypto
|
|
523
|
+
or add enable_extension "pgcrypto" to a migration.
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
If an unknown database name is passed:
|
|
527
|
+
|
|
528
|
+
```
|
|
529
|
+
error: Unknown database "bad_name". Available for "development": primary, postgres, mysql
|
|
530
|
+
```
|
metadata
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: custom_id
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Pawel Niemczyk
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: activesupport
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '7.0'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '9'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '7.0'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '9'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: railties
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '7.0'
|
|
59
|
+
- - "<"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '9'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '7.0'
|
|
69
|
+
- - "<"
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '9'
|
|
72
|
+
description: |
|
|
73
|
+
CustomId generates unique, human-readable, prefixed string IDs (e.g. "usr_7xKmN2pQ…")
|
|
74
|
+
for ActiveRecord models. Inspired by Stripe-style identifiers. Supports embedding
|
|
75
|
+
shared characters from related model IDs, custom target columns, configurable
|
|
76
|
+
random-part length, and an optional PostgreSQL trigger-based alternative.
|
|
77
|
+
email:
|
|
78
|
+
- pawel@way2do.it
|
|
79
|
+
executables: []
|
|
80
|
+
extensions: []
|
|
81
|
+
extra_rdoc_files: []
|
|
82
|
+
files:
|
|
83
|
+
- AGENTS.md
|
|
84
|
+
- CHANGELOG.md
|
|
85
|
+
- CLAUDE.md
|
|
86
|
+
- LICENSE.txt
|
|
87
|
+
- README.md
|
|
88
|
+
- lib/custom_id.rb
|
|
89
|
+
- lib/custom_id/concern.rb
|
|
90
|
+
- lib/custom_id/db_extension.rb
|
|
91
|
+
- lib/custom_id/installer.rb
|
|
92
|
+
- lib/custom_id/railtie.rb
|
|
93
|
+
- lib/custom_id/version.rb
|
|
94
|
+
- lib/tasks/custom_id.rake
|
|
95
|
+
- llms/overview.md
|
|
96
|
+
- llms/usage.md
|
|
97
|
+
homepage: https://github.com/pniemczyk/custom_id
|
|
98
|
+
licenses:
|
|
99
|
+
- MIT
|
|
100
|
+
metadata:
|
|
101
|
+
homepage_uri: https://github.com/pniemczyk/custom_id
|
|
102
|
+
source_code_uri: https://github.com/pniemczyk/custom_id
|
|
103
|
+
changelog_uri: https://github.com/pniemczyk/custom_id/blob/main/CHANGELOG.md
|
|
104
|
+
rubygems_mfa_required: 'true'
|
|
105
|
+
rdoc_options: []
|
|
106
|
+
require_paths:
|
|
107
|
+
- lib
|
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: 3.2.0
|
|
113
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
|
+
requirements:
|
|
115
|
+
- - ">="
|
|
116
|
+
- !ruby/object:Gem::Version
|
|
117
|
+
version: '0'
|
|
118
|
+
requirements: []
|
|
119
|
+
rubygems_version: 3.6.9
|
|
120
|
+
specification_version: 4
|
|
121
|
+
summary: Prefixed, Stripe-style custom IDs for ActiveRecord models
|
|
122
|
+
test_files: []
|