sqlite_crypto 1.0.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/CHANGELOG.md +67 -0
- data/LICENSE.txt +21 -0
- data/README.md +295 -0
- data/lib/sqlite_crypto/migration_helpers.rb +61 -0
- data/lib/sqlite_crypto/model_extensions.rb +29 -0
- data/lib/sqlite_crypto/railtie.rb +30 -0
- data/lib/sqlite_crypto/schema_dumper.rb +17 -0
- data/lib/sqlite_crypto/type/base.rb +38 -0
- data/lib/sqlite_crypto/type/ulid.rb +19 -0
- data/lib/sqlite_crypto/type/uuid.rb +19 -0
- data/lib/sqlite_crypto/version.rb +7 -0
- data/lib/sqlite_crypto.rb +13 -0
- metadata +194 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 81485ab3edd90e39dd41fb1c55ebf1342628a233d69f7adc6e52c04f1f507c6f
|
|
4
|
+
data.tar.gz: de617f3d572a0113e0d82f372c8b775c1920d3c1637ae3f19ab32d4dd92446fc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 633d6fe21b1507f46af710c4732ffd18822ecc8639de6a625dbd9f0e03a2d032c461241279a834affab5f9008cf52b3364983506d2819176820b30392c53ad2b
|
|
7
|
+
data.tar.gz: f98d8c7b022ba486f0125802651e1fb40f68bf413b4291b527349b9b4bd4c8683c99faca49eeafb9246668a84e8db543291d93fd38652006d8d91b3737edb621
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-12-20
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Type System**: Custom UUID and ULID ActiveRecord types for SQLite adapter
|
|
12
|
+
- UUID type with validation (36-character hyphenated format)
|
|
13
|
+
- ULID type with time-sortable validation (26-character format)
|
|
14
|
+
- Shared Type::Base class for DRY implementation
|
|
15
|
+
- **Migration Helpers**: DSL methods for migrations
|
|
16
|
+
- `t.uuid()` and `t.ulid()` for column definitions
|
|
17
|
+
- Automatic foreign key type detection for `references`/`belongs_to`
|
|
18
|
+
- Support for `id: :uuid` and `id: :ulid` shorthand syntax in create_table
|
|
19
|
+
- `:to_table` option support for non-standard table names
|
|
20
|
+
- **Schema Dumper Integration**: Clean schema.rb output
|
|
21
|
+
- Outputs `id: :uuid` instead of `id: { type: :string, limit: 36 }`
|
|
22
|
+
- Outputs `id: :ulid` instead of `id: { type: :string, limit: 26 }`
|
|
23
|
+
- Preserves standard integer primary keys unchanged
|
|
24
|
+
- **Model Extensions**: ActiveRecord class methods for automatic UUID/ULID generation
|
|
25
|
+
- `generates_uuid(attribute, unique: false)` - Auto-generates SecureRandom.uuid on create
|
|
26
|
+
- `generates_ulid(attribute, unique: false)` - Auto-generates time-sortable ULID on create
|
|
27
|
+
- Optional uniqueness validation with `unique: true` parameter
|
|
28
|
+
- Preserves existing values (uses `||=` to avoid overwriting)
|
|
29
|
+
- Available to all ActiveRecord models via concern
|
|
30
|
+
- **Rails Integration**: Full Railtie implementation
|
|
31
|
+
- Type registration for SQLite3 adapter
|
|
32
|
+
- Schema dumper prepending with proper load order
|
|
33
|
+
- Migration helpers loading
|
|
34
|
+
- Model extensions loading after database initialization
|
|
35
|
+
- **Testing Infrastructure**:
|
|
36
|
+
- Comprehensive test suite with 99.01% code coverage (58 examples)
|
|
37
|
+
- Unit tests for UUID/ULID types with validation
|
|
38
|
+
- Integration tests for real-world migration scenarios
|
|
39
|
+
- Model extension tests covering generation, validation, and edge cases
|
|
40
|
+
- Performance benchmarks comparing Integer vs UUID vs ULID
|
|
41
|
+
- Support for Rails 7.1, 7.2, 8.0, and 8.1
|
|
42
|
+
- Security audit with bundle-audit in CI
|
|
43
|
+
- **Performance Benchmarks**: Comprehensive benchmarking suite
|
|
44
|
+
- Insert performance comparison
|
|
45
|
+
- Query performance benchmarks (find, where)
|
|
46
|
+
- Storage requirement analysis
|
|
47
|
+
- ID format demonstrations
|
|
48
|
+
- Use case recommendations with real-world examples
|
|
49
|
+
- Security implications documentation
|
|
50
|
+
- **Documentation**: Professional README with badges, examples, and benchmarks
|
|
51
|
+
- 99.01% test coverage badge
|
|
52
|
+
- Ruby/Rails compatibility matrix
|
|
53
|
+
- Complete usage examples for all features
|
|
54
|
+
- Migration guide for existing applications
|
|
55
|
+
- Benchmark results and recommendations
|
|
56
|
+
- Security considerations
|
|
57
|
+
- Branch protection rules and contribution guidelines
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
- Improved pluralization handling using Rails' built-in `pluralize` method
|
|
61
|
+
- Refactored type system with shared Type::Base class
|
|
62
|
+
- Organized specs: separated integration tests from unit tests
|
|
63
|
+
|
|
64
|
+
### Fixed
|
|
65
|
+
- Rails version compatibility for migrations (supports Rails 7.1 through 8.1)
|
|
66
|
+
- Schema dumper prepend timing by loading sqlite3_adapter explicitly
|
|
67
|
+
- ULID foreign key detection with proper table name pluralization
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Bartosz Ozdoba
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# SQLite crypto
|
|
2
|
+
|
|
3
|
+
[](https://github.com/bart-oz/sqlite_crypto/releases)
|
|
4
|
+
[](LICENSE.txt)
|
|
5
|
+
[](https://github.com/bart-oz/sqlite_crypto/actions)
|
|
6
|
+
[](https://github.com/bart-oz/sqlite_crypto/actions)
|
|
7
|
+
[](https://github.com/bart-oz/sqlite_crypto)
|
|
8
|
+
|
|
9
|
+
Seamless UUID and ULID primary key support for Rails with SQLite3.
|
|
10
|
+
|
|
11
|
+
### ID Format Comparison
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
INTEGER: 1, 2, 3, ... (sequential, guessable)
|
|
15
|
+
UUID: 550e8400-e29b-41d4-a716-446655440000 (random, 36 chars)
|
|
16
|
+
ULID: 01ARZ3NDEKTSV4RRFFQ69G5FAV (time-sortable, 26 chars)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Why Use UUID/ULID Instead of Integer IDs?
|
|
20
|
+
|
|
21
|
+
| | **Integer** | **UUID** | **ULID** |
|
|
22
|
+
|---|-------------|----------|----------|
|
|
23
|
+
| **Performance** | Baseline | +2-5% slower | +5-10% slower |
|
|
24
|
+
| **Storage** | 8 bytes | 36 bytes (4.5x) | 26 bytes (3.2x) |
|
|
25
|
+
| **Security** | Guessable | Random | Random |
|
|
26
|
+
| **Collisions** | ⚠️ High in distributed systems | Virtually impossible | Virtually impossible |
|
|
27
|
+
| **Sortable** | Sequential | Random | Time-based |
|
|
28
|
+
| **Distributed** | Needs coordination | Generate anywhere | Generate anywhere |
|
|
29
|
+
|
|
30
|
+
**Performance testing**: Run `bundle exec rspec --tag performance` to benchmark on your hardware. Specs test scaling from 100 → 10,000 records across inserts, queries, updates, and deletes.
|
|
31
|
+
|
|
32
|
+
## Gem Compatibility
|
|
33
|
+
|
|
34
|
+
| Ruby Version | Rails 7.1 | Rails 7.2 | Rails 8.0 | Rails 8.1 |
|
|
35
|
+
|--------------|-----------|-----------|-----------|-----------|
|
|
36
|
+
| 3.1 | ✅ | ✅ | ❌ | ❌ |
|
|
37
|
+
| 3.2 | ✅ | ✅ | ✅ | ✅ |
|
|
38
|
+
| 3.3 | ✅ | ✅ | ✅ | ✅ |
|
|
39
|
+
| 3.4 | ✅ | ✅ | ✅ | ✅ |
|
|
40
|
+
|
|
41
|
+
**Recommended**: Ruby 3.3+ with Rails 8.0+
|
|
42
|
+
|
|
43
|
+
**Support Policy**: Actively maintained with updates for new Ruby and Rails versions.
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
* UUID primary keys with automatic validation
|
|
48
|
+
* ULID primary keys with time-sortable validation
|
|
49
|
+
* Migration DSL helpers (`t.uuid`, `t.ulid`)
|
|
50
|
+
* Automatic foreign key type detection
|
|
51
|
+
* Model extensions for UUID/ULID generation
|
|
52
|
+
* Clean schema.rb output
|
|
53
|
+
* Zero configuration required
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
Add to your Gemfile:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
gem "sqlite_crypto"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then run:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bundle install
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
That's it! No generators or configuration needed.
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
### UUID Primary Keys
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
class CreateUsers < ActiveRecord::Migration[8.1]
|
|
77
|
+
def change
|
|
78
|
+
create_table :users, id: :uuid do |t|
|
|
79
|
+
t.string :email
|
|
80
|
+
t.string :name
|
|
81
|
+
t.timestamps
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### ULID Primary Keys
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
class CreatePosts < ActiveRecord::Migration[8.1]
|
|
91
|
+
def change
|
|
92
|
+
create_table :posts, id: :ulid do |t|
|
|
93
|
+
t.string :title
|
|
94
|
+
t.text :content
|
|
95
|
+
t.timestamps
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### UUID/ULID Columns
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
class AddTrackingIds < ActiveRecord::Migration[8.1]
|
|
105
|
+
def change
|
|
106
|
+
change_table :orders do |t|
|
|
107
|
+
t.uuid :external_id
|
|
108
|
+
t.ulid :tracking_number
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Foreign Keys (Automatic Detection)
|
|
115
|
+
|
|
116
|
+
The gem automatically detects UUID/ULID primary keys and creates matching foreign keys:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
# Users table has UUID primary key
|
|
120
|
+
create_table :users, id: :uuid do |t|
|
|
121
|
+
t.string :name
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Posts automatically get varchar(36) user_id foreign key
|
|
125
|
+
create_table :posts do |t|
|
|
126
|
+
t.references :user # Automatically creates varchar(36) foreign key!
|
|
127
|
+
t.string :title
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Works with ULID too:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# Categories table has ULID primary key
|
|
135
|
+
create_table :categories, id: :ulid do |t|
|
|
136
|
+
t.string :name
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Articles automatically get varchar(26) category_id foreign key
|
|
140
|
+
create_table :articles do |t|
|
|
141
|
+
t.references :category # Automatically creates varchar(26) foreign key!
|
|
142
|
+
t.string :title
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Custom Table Names
|
|
147
|
+
|
|
148
|
+
Use `:to_table` option for non-standard table names:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
create_table :posts do |t|
|
|
152
|
+
t.references :author, to_table: :users # Uses users table's UUID type
|
|
153
|
+
t.string :title
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Model Extensions (Auto-Generate UUIDs/ULIDs)
|
|
158
|
+
|
|
159
|
+
Automatically generate UUID or ULID values for any column:
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
class User < ApplicationRecord
|
|
163
|
+
# Generate UUID for 'token' column on create
|
|
164
|
+
generates_uuid :token
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
class Order < ApplicationRecord
|
|
168
|
+
# Generate ULID for 'reference' column with uniqueness validation
|
|
169
|
+
generates_ulid :reference, unique: true
|
|
170
|
+
end
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Features:**
|
|
174
|
+
- `generates_uuid(attribute, unique: false)` - Generates SecureRandom.uuid
|
|
175
|
+
- `generates_ulid(attribute, unique: false)` - Generates time-sortable ULID
|
|
176
|
+
- `unique: true` - Adds uniqueness validation
|
|
177
|
+
- Preserves existing values (won't overwrite if already set)
|
|
178
|
+
- Works with any string column, not just primary keys
|
|
179
|
+
|
|
180
|
+
**Example migration:**
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
class AddTokenToUsers < ActiveRecord::Migration[8.1]
|
|
184
|
+
def change
|
|
185
|
+
add_column :users, :token, :string, limit: 36
|
|
186
|
+
add_index :users, :token, unique: true
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Schema Output
|
|
192
|
+
|
|
193
|
+
Your `db/schema.rb` will be clean and readable:
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
create_table "users", id: :uuid, force: :cascade do |t|
|
|
197
|
+
t.string "email"
|
|
198
|
+
t.timestamps
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
create_table "posts", force: :cascade do |t|
|
|
202
|
+
t.string "user_id", limit: 36 # Clean foreign key
|
|
203
|
+
t.string "title"
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## How It Works
|
|
208
|
+
|
|
209
|
+
1. **Type Registration**: Registers `:uuid` and `:ulid` types with ActiveRecord for SQLite3
|
|
210
|
+
2. **Validation**: UUIDs validate 36-char format, ULIDs validate 26-char format
|
|
211
|
+
3. **Migration Helpers**: `t.uuid()` and `t.ulid()` methods in migrations
|
|
212
|
+
4. **Smart References**: `t.references` detects parent table's primary key type
|
|
213
|
+
5. **Model Extensions**: `generates_uuid` and `generates_ulid` for automatic generation
|
|
214
|
+
6. **Schema Dumper**: Outputs clean `id: :uuid` instead of verbose type definitions
|
|
215
|
+
|
|
216
|
+
## Requirements
|
|
217
|
+
|
|
218
|
+
- Rails 7.1+ (tested on 7.1, 7.2, 8.0, 8.1)
|
|
219
|
+
- Ruby 3.1+
|
|
220
|
+
- SQLite3
|
|
221
|
+
|
|
222
|
+
## Migrating Existing Apps
|
|
223
|
+
|
|
224
|
+
### New Tables Only (Recommended)
|
|
225
|
+
|
|
226
|
+
The safest approach is to use UUID/ULID only for new tables:
|
|
227
|
+
|
|
228
|
+
```ruby
|
|
229
|
+
# Existing tables keep integer IDs
|
|
230
|
+
# users: id (integer)
|
|
231
|
+
# posts: id (integer), user_id (integer)
|
|
232
|
+
|
|
233
|
+
# New tables use UUID/ULID
|
|
234
|
+
create_table :invoices, id: :uuid do |t|
|
|
235
|
+
t.references :user # Still integer (auto-detected from users table)
|
|
236
|
+
t.decimal :amount
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
create_table :sessions, id: :ulid do |t|
|
|
240
|
+
t.references :user # Still integer
|
|
241
|
+
t.string :token
|
|
242
|
+
end
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Advanced Patterns
|
|
246
|
+
|
|
247
|
+
### ID Prefixes (Optional)
|
|
248
|
+
|
|
249
|
+
For Stripe-style prefixed IDs (`inv_`, `usr_`, etc.), add to your models:
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
class Invoice < ApplicationRecord
|
|
253
|
+
before_create :generate_prefixed_id
|
|
254
|
+
|
|
255
|
+
private
|
|
256
|
+
|
|
257
|
+
def generate_prefixed_id
|
|
258
|
+
self.id = "inv_#{SecureRandom.uuid}" if id.nil?
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Mixing Types
|
|
264
|
+
|
|
265
|
+
You can use different primary key types in the same app:
|
|
266
|
+
|
|
267
|
+
```ruby
|
|
268
|
+
create_table :users, id: :uuid do |t|
|
|
269
|
+
t.string :email
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
create_table :sessions, id: :ulid do |t|
|
|
273
|
+
t.string :token
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
create_table :logs do |t| # Standard integer ID
|
|
277
|
+
t.string :message
|
|
278
|
+
end
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Development
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
bundle install
|
|
285
|
+
bundle exec rspec
|
|
286
|
+
bundle exec standardrb
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Contributing
|
|
290
|
+
|
|
291
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT License - see [LICENSE.txt](LICENSE.txt)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record/connection_adapters/sqlite3_adapter"
|
|
4
|
+
|
|
5
|
+
module SqliteCrypto
|
|
6
|
+
module MigrationHelpers
|
|
7
|
+
module TableDefinition
|
|
8
|
+
def uuid(name, **options)
|
|
9
|
+
column(name, :uuid, **options)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def ulid(name, **options)
|
|
13
|
+
column(name, :ulid, **options)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module References
|
|
18
|
+
def references(*args, **options)
|
|
19
|
+
ref_name = args.first
|
|
20
|
+
ref_table = options.delete(:to_table) || ref_name.to_s.pluralize
|
|
21
|
+
|
|
22
|
+
if (primary_key_type = detect_primary_key_type(ref_table))
|
|
23
|
+
options[:type] ||= :string
|
|
24
|
+
options[:limit] ||= (primary_key_type == :uuid) ? 36 : 26
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
alias_method :belongs_to, :references
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def detect_primary_key_type(table_name)
|
|
35
|
+
conn = @conn || @base || (respond_to?(:connection) ? connection : nil)
|
|
36
|
+
return nil unless conn&.table_exists?(table_name)
|
|
37
|
+
|
|
38
|
+
pk_column = find_primary_key_column(table_name, conn)
|
|
39
|
+
return nil unless pk_column
|
|
40
|
+
|
|
41
|
+
case pk_column.sql_type.downcase
|
|
42
|
+
when "varchar(36)", "uuid" then :uuid
|
|
43
|
+
when "varchar(26)", "ulid" then :ulid
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def find_primary_key_column(table_name, conn)
|
|
48
|
+
pk_name = conn.primary_key(table_name)
|
|
49
|
+
return nil unless pk_name
|
|
50
|
+
|
|
51
|
+
conn.columns(table_name).find { |c| c.name == pk_name }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Extend ActiveRecord classes
|
|
58
|
+
ActiveRecord::ConnectionAdapters::SQLite3::TableDefinition.include(SqliteCrypto::MigrationHelpers::TableDefinition)
|
|
59
|
+
ActiveRecord::ConnectionAdapters::SQLite3::TableDefinition.prepend(SqliteCrypto::MigrationHelpers::References)
|
|
60
|
+
ActiveRecord::ConnectionAdapters::Table.include(SqliteCrypto::MigrationHelpers::TableDefinition)
|
|
61
|
+
ActiveRecord::ConnectionAdapters::Table.prepend(SqliteCrypto::MigrationHelpers::References)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ulid"
|
|
4
|
+
|
|
5
|
+
module SqliteCrypto
|
|
6
|
+
module ModelExtensions
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
def generates_uuid(attribute, unique: false)
|
|
11
|
+
before_create do
|
|
12
|
+
self[attribute] ||= SecureRandom.uuid
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
validates attribute, uniqueness: true if unique
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def generates_ulid(attribute, unique: false)
|
|
19
|
+
before_create do
|
|
20
|
+
self[attribute] ||= ULID.generate.to_s
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
validates attribute, uniqueness: true if unique
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
ActiveRecord::Base.include(SqliteCrypto::ModelExtensions) if defined?(ActiveRecord::Base)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sqlite_crypto/type/uuid"
|
|
4
|
+
require "sqlite_crypto/type/ulid"
|
|
5
|
+
|
|
6
|
+
module SqliteCrypto
|
|
7
|
+
class Railtie < ::Rails::Railtie
|
|
8
|
+
# Configuration namespace for users to set options
|
|
9
|
+
config.sqlite_crypto = ActiveSupport::OrderedOptions.new
|
|
10
|
+
|
|
11
|
+
initializer "sqlite_crypto.register_types" do
|
|
12
|
+
ActiveRecord::Type.register(:uuid, SqliteCrypto::Type::Uuid, adapter: :sqlite3)
|
|
13
|
+
ActiveRecord::Type.register(:ulid, SqliteCrypto::Type::ULID, adapter: :sqlite3)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
initializer "sqlite_crypto.schema_dumper", after: "active_record.initialize_database" do
|
|
17
|
+
require "active_record/connection_adapters/sqlite3_adapter"
|
|
18
|
+
require "sqlite_crypto/schema_dumper"
|
|
19
|
+
ActiveRecord::ConnectionAdapters::SQLite3::SchemaDumper.prepend(SqliteCrypto::SchemaDumper)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
initializer "sqlite_crypto.migration_helpers", after: "active_record.initialize_database" do
|
|
23
|
+
require "sqlite_crypto/migration_helpers"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
initializer "sqlite_crypto.model_extensions", after: "active_record.initialize_database" do
|
|
27
|
+
require "sqlite_crypto/model_extensions"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqliteCrypto
|
|
4
|
+
module SchemaDumper
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def column_spec_for_primary_key(column)
|
|
8
|
+
return super unless column.name == "id" && column.type == :string
|
|
9
|
+
|
|
10
|
+
case column.limit
|
|
11
|
+
when 36 then {id: :uuid}
|
|
12
|
+
when 26 then {id: :ulid}
|
|
13
|
+
else super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SqliteCrypto
|
|
4
|
+
module Type
|
|
5
|
+
class Base < ActiveRecord::Type::String
|
|
6
|
+
def deserialize(value)
|
|
7
|
+
return if value.nil?
|
|
8
|
+
cast(value)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def cast(value)
|
|
12
|
+
return if value.nil?
|
|
13
|
+
return value if value.is_a?(String) && valid?(value)
|
|
14
|
+
|
|
15
|
+
if value.respond_to?(:to_s)
|
|
16
|
+
str = value.to_s
|
|
17
|
+
return str if valid?(str)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
raise ArgumentError, "Invalid #{type.upcase}: #{value.inspect}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def serialize(value)
|
|
24
|
+
cast(value)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def changed_in_place?(raw_old_value, new_value)
|
|
28
|
+
cast(raw_old_value) != cast(new_value)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def valid?(value)
|
|
34
|
+
raise NotImplementedError, "Subclasses must implement #valid?"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sqlite_crypto/type/base"
|
|
4
|
+
|
|
5
|
+
module SqliteCrypto
|
|
6
|
+
module Type
|
|
7
|
+
class ULID < Base
|
|
8
|
+
def type
|
|
9
|
+
:ulid
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def valid?(value)
|
|
15
|
+
value.match?(/^[0-7][0-9A-Z]{25}$/i)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sqlite_crypto/type/base"
|
|
4
|
+
|
|
5
|
+
module SqliteCrypto
|
|
6
|
+
module Type
|
|
7
|
+
class Uuid < Base
|
|
8
|
+
def type
|
|
9
|
+
:uuid
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def valid?(value)
|
|
15
|
+
value.match?(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sqlite_crypto/version"
|
|
4
|
+
require "sqlite_crypto/railtie" if defined?(Rails)
|
|
5
|
+
require "sqlite_crypto/schema_dumper" if defined?(ActiveRecord)
|
|
6
|
+
|
|
7
|
+
module SqliteCrypto
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
def self.load_extensions
|
|
11
|
+
# Placeholder for future extension loading logic
|
|
12
|
+
end
|
|
13
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sqlite_crypto
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- BartOz
|
|
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: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 7.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 7.1.0
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: sqlite3
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 1.6.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 1.6.0
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ulid
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: bundler
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '2.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '2.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '12.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '12.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rspec-rails
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '6.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '6.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: simplecov
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0.22'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0.22'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: sqlite3
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: 1.6.0
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: 1.6.0
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: standard
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '1.30'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '1.30'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: appraisal
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '2.5'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '2.5'
|
|
152
|
+
description: A lightweight, modular gem providing transparent UUID/ULID primary key
|
|
153
|
+
configuration for Rails applications using SQLite.
|
|
154
|
+
email:
|
|
155
|
+
- bartek.ozdoba@gmail.com
|
|
156
|
+
executables: []
|
|
157
|
+
extensions: []
|
|
158
|
+
extra_rdoc_files: []
|
|
159
|
+
files:
|
|
160
|
+
- CHANGELOG.md
|
|
161
|
+
- LICENSE.txt
|
|
162
|
+
- README.md
|
|
163
|
+
- lib/sqlite_crypto.rb
|
|
164
|
+
- lib/sqlite_crypto/migration_helpers.rb
|
|
165
|
+
- lib/sqlite_crypto/model_extensions.rb
|
|
166
|
+
- lib/sqlite_crypto/railtie.rb
|
|
167
|
+
- lib/sqlite_crypto/schema_dumper.rb
|
|
168
|
+
- lib/sqlite_crypto/type/base.rb
|
|
169
|
+
- lib/sqlite_crypto/type/ulid.rb
|
|
170
|
+
- lib/sqlite_crypto/type/uuid.rb
|
|
171
|
+
- lib/sqlite_crypto/version.rb
|
|
172
|
+
homepage: https://github.com/bart-oz/sqlite_crypto
|
|
173
|
+
licenses:
|
|
174
|
+
- MIT
|
|
175
|
+
metadata:
|
|
176
|
+
rubygems_mfa_required: 'true'
|
|
177
|
+
rdoc_options: []
|
|
178
|
+
require_paths:
|
|
179
|
+
- lib
|
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
181
|
+
requirements:
|
|
182
|
+
- - ">="
|
|
183
|
+
- !ruby/object:Gem::Version
|
|
184
|
+
version: 3.1.0
|
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
|
+
requirements:
|
|
187
|
+
- - ">="
|
|
188
|
+
- !ruby/object:Gem::Version
|
|
189
|
+
version: '0'
|
|
190
|
+
requirements: []
|
|
191
|
+
rubygems_version: 3.6.9
|
|
192
|
+
specification_version: 4
|
|
193
|
+
summary: Seamless UUID/ULID support for Rails 8 with SQLite
|
|
194
|
+
test_files: []
|