familia 2.0.0.pre26 → 2.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 +4 -4
- data/CHANGELOG.rst +49 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -2
- data/README.md +1 -3
- data/docs/guides/feature-encrypted-fields.md +1 -1
- data/docs/guides/feature-expiration.md +1 -1
- data/docs/guides/feature-quantization.md +1 -1
- data/docs/overview.md +7 -7
- data/docs/reference/api-technical.md +103 -7
- data/familia.gemspec +1 -2
- data/lib/familia/data_type/types/hashkey.rb +238 -0
- data/lib/familia/data_type/types/listkey.rb +110 -4
- data/lib/familia/data_type/types/sorted_set.rb +365 -0
- data/lib/familia/data_type/types/stringkey.rb +139 -0
- data/lib/familia/data_type/types/unsorted_set.rb +122 -2
- data/lib/familia/version.rb +1 -1
- metadata +2 -27
- data/docs/migrating/v2.0.0-pre.md +0 -84
- data/docs/migrating/v2.0.0-pre11.md +0 -253
- data/docs/migrating/v2.0.0-pre12.md +0 -306
- data/docs/migrating/v2.0.0-pre13.md +0 -95
- data/docs/migrating/v2.0.0-pre14.md +0 -37
- data/docs/migrating/v2.0.0-pre18.md +0 -58
- data/docs/migrating/v2.0.0-pre19.md +0 -197
- data/docs/migrating/v2.0.0-pre22.md +0 -241
- data/docs/migrating/v2.0.0-pre5.md +0 -131
- data/docs/migrating/v2.0.0-pre6.md +0 -154
- data/docs/migrating/v2.0.0-pre7.md +0 -222
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
# Migrating Guide: v2.0.0-pre12
|
|
2
|
-
|
|
3
|
-
This version introduces significant security improvements to Familia's identifier system, including verifiable identifiers with HMAC signatures, scoped identifier namespaces, and hardened external identifier derivation to prevent potential security vulnerabilities.
|
|
4
|
-
|
|
5
|
-
## VerifiableIdentifier Feature
|
|
6
|
-
|
|
7
|
-
### Overview
|
|
8
|
-
|
|
9
|
-
The new `Familia::VerifiableIdentifier` module allows applications to create and verify identifiers with embedded HMAC signatures. This enables stateless confirmation that an identifier was generated by your application, preventing forged IDs from malicious sources.
|
|
10
|
-
|
|
11
|
-
### Basic Usage
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
class Customer < Familia::Horreum
|
|
15
|
-
feature :verifiable_identifier
|
|
16
|
-
|
|
17
|
-
# Required: UnsortedSet the HMAC secret (do this once in your app initialization)
|
|
18
|
-
# Generate with: SecureRandom.hex(64)
|
|
19
|
-
ENV['VERIFIABLE_ID_HMAC_SECRET'] = 'your_64_character_hex_secret'
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Generate a verifiable identifier
|
|
23
|
-
customer = Customer.new
|
|
24
|
-
verifiable_id = customer.generate_verifiable_id
|
|
25
|
-
# => "cust_1234567890abcdef_a1b2c3d4e5f6789..."
|
|
26
|
-
|
|
27
|
-
# Verify the identifier later (stateless verification)
|
|
28
|
-
if Customer.verified_identifier?(verifiable_id)
|
|
29
|
-
# Identifier is valid and was generated by this application
|
|
30
|
-
original_id = Customer.extract_identifier(verifiable_id)
|
|
31
|
-
customer = Customer.new(original_id)
|
|
32
|
-
else
|
|
33
|
-
# Identifier is forged or corrupted
|
|
34
|
-
raise SecurityError, "Invalid identifier"
|
|
35
|
-
end
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Scoped VerifiableIdentifier
|
|
39
|
-
|
|
40
|
-
The new `scope` parameter enables cryptographically isolated identifier namespaces for multi-tenant, multi-domain, or multi-environment applications.
|
|
41
|
-
|
|
42
|
-
#### Before (Global Scope)
|
|
43
|
-
```ruby
|
|
44
|
-
# All identifiers share the same cryptographic space
|
|
45
|
-
admin_id = admin.generate_verifiable_id
|
|
46
|
-
user_id = user.generate_verifiable_id
|
|
47
|
-
|
|
48
|
-
# Risk: Cross-contamination between different contexts
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
#### After (Scoped Namespaces)
|
|
52
|
-
```ruby
|
|
53
|
-
# Production environment
|
|
54
|
-
prod_customer_id = customer.generate_verifiable_id(scope: 'production')
|
|
55
|
-
prod_admin_id = admin.generate_verifiable_id(scope: 'production:admin')
|
|
56
|
-
|
|
57
|
-
# Development environment
|
|
58
|
-
dev_customer_id = customer.generate_verifiable_id(scope: 'development')
|
|
59
|
-
|
|
60
|
-
# Multi-tenant application
|
|
61
|
-
tenant_a_id = user.generate_verifiable_id(scope: "tenant:#{tenant_a.id}")
|
|
62
|
-
tenant_b_id = user.generate_verifiable_id(scope: "tenant:#{tenant_b.id}")
|
|
63
|
-
|
|
64
|
-
# Verification requires matching scope
|
|
65
|
-
Customer.verified_identifier?(prod_customer_id, scope: 'production') # => true
|
|
66
|
-
Customer.verified_identifier?(prod_customer_id, scope: 'development') # => false
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**Scope Benefits:**
|
|
70
|
-
- **Multi-tenant isolation**: Tenant A cannot forge identifiers for Tenant B
|
|
71
|
-
- **Environment separation**: Production IDs cannot be used in development
|
|
72
|
-
- **Role-based security**: Admin scopes separate from user scopes
|
|
73
|
-
- **Full backward compatibility**: Existing code without scopes continues to work
|
|
74
|
-
|
|
75
|
-
### Key Management
|
|
76
|
-
|
|
77
|
-
#### Secure Secret Generation
|
|
78
|
-
```ruby
|
|
79
|
-
# Generate a cryptographically secure HMAC secret
|
|
80
|
-
require 'securerandom'
|
|
81
|
-
secret = SecureRandom.hex(64) # 512-bit secret
|
|
82
|
-
puts "VERIFIABLE_ID_HMAC_SECRET=#{secret}"
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
#### Environment Configuration
|
|
86
|
-
```ruby
|
|
87
|
-
# config/application.rb or equivalent
|
|
88
|
-
# UnsortedSet this BEFORE any VerifiableIdentifier usage
|
|
89
|
-
ENV['VERIFIABLE_ID_HMAC_SECRET'] = Rails.application.credentials.verifiable_id_secret
|
|
90
|
-
|
|
91
|
-
# Or configure programmatically
|
|
92
|
-
Familia::VerifiableIdentifier.hmac_secret = your_secret_string
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## ObjectIdentifier Feature Improvements
|
|
96
|
-
|
|
97
|
-
### Method Renaming
|
|
98
|
-
|
|
99
|
-
Method names have been updated for clarity and consistency:
|
|
100
|
-
|
|
101
|
-
#### Before
|
|
102
|
-
```ruby
|
|
103
|
-
customer = Customer.new
|
|
104
|
-
objid = customer.generate_objid # Unclear what this generates
|
|
105
|
-
extid = Customer.generate_extid(objid) # Less secure class method
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
#### After
|
|
109
|
-
```ruby
|
|
110
|
-
customer = Customer.new
|
|
111
|
-
objid = customer.generate_object_identifier # Clear: generates object ID
|
|
112
|
-
extid = customer.derive_external_identifier # Clear: derives from objid, instance method
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
**Migration:**
|
|
116
|
-
- Replace `generate_objid` → `generate_object_identifier`
|
|
117
|
-
- Replace `generate_external_identifier` → `derive_external_identifier`
|
|
118
|
-
- Remove usage of `generate_extid` (deprecated for security reasons)
|
|
119
|
-
|
|
120
|
-
### Provenance Tracking
|
|
121
|
-
|
|
122
|
-
ObjectIdentifier now tracks which generator was used for each identifier:
|
|
123
|
-
|
|
124
|
-
```ruby
|
|
125
|
-
class Customer < Familia::Horreum
|
|
126
|
-
feature :object_identifier
|
|
127
|
-
|
|
128
|
-
# Configure generator type
|
|
129
|
-
object_identifier_generator :uuid_v7 # or :uuid_v4, :hex, custom proc
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
customer = Customer.new
|
|
133
|
-
objid = customer.generate_object_identifier
|
|
134
|
-
|
|
135
|
-
# Provenance information available
|
|
136
|
-
puts customer.object_identifier_generator_type # => :uuid_v7
|
|
137
|
-
puts customer.objid_format # => :uuid (normalized format)
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
**Benefits:**
|
|
141
|
-
- **Security auditing**: Know which generator created each identifier
|
|
142
|
-
- **Format normalization**: Eliminates ambiguity between UUID and hex formats
|
|
143
|
-
- **Migration support**: Track mixed generator usage during transitions
|
|
144
|
-
|
|
145
|
-
## ExternalIdentifier Security Hardening
|
|
146
|
-
|
|
147
|
-
### Provenance Validation
|
|
148
|
-
|
|
149
|
-
ExternalIdentifier now validates that objid values come from the ObjectIdentifier feature before deriving external identifiers.
|
|
150
|
-
|
|
151
|
-
#### Before (Potential Security Risk)
|
|
152
|
-
```ruby
|
|
153
|
-
# Could derive external IDs from any string, including malicious input
|
|
154
|
-
extid = customer.derive_external_identifier("malicious_input")
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
#### After (Hardened)
|
|
158
|
-
```ruby
|
|
159
|
-
customer = Customer.new
|
|
160
|
-
customer.generate_object_identifier # Must generate objid first
|
|
161
|
-
|
|
162
|
-
# Only works with validated objid from ObjectIdentifier feature
|
|
163
|
-
extid = customer.derive_external_identifier # Secure: uses validated objid
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Improved Security Model
|
|
167
|
-
|
|
168
|
-
External identifiers are now derived using the internal objid as a seed for a new random value, rather than directly deriving from objid.
|
|
169
|
-
|
|
170
|
-
#### Before
|
|
171
|
-
```ruby
|
|
172
|
-
# Direct derivation could leak information about objid
|
|
173
|
-
extid = hash(objid) # Information leakage risk
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
#### After
|
|
177
|
-
```ruby
|
|
178
|
-
# objid used as seed for new random value
|
|
179
|
-
extid = secure_hash(objid + additional_entropy) # No information leakage
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Error Handling Improvements
|
|
183
|
-
|
|
184
|
-
External identifier now raises clear errors for invalid usage:
|
|
185
|
-
|
|
186
|
-
```ruby
|
|
187
|
-
class Customer < Familia::Horreum
|
|
188
|
-
feature :external_identifier # Missing: object_identifier dependency
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
customer = Customer.new
|
|
192
|
-
# Raises ExternalIdentifierError instead of returning nil
|
|
193
|
-
customer.derive_external_identifier
|
|
194
|
-
# => Familia::ExternalIdentifierError: Model does not have an objid field
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Migration Steps
|
|
198
|
-
|
|
199
|
-
### 1. Update Method Names
|
|
200
|
-
|
|
201
|
-
Replace deprecated method names in your codebase:
|
|
202
|
-
|
|
203
|
-
```bash
|
|
204
|
-
# Search and replace patterns:
|
|
205
|
-
grep -r "generate_objid" --include="*.rb" .
|
|
206
|
-
# Replace with: generate_object_identifier
|
|
207
|
-
|
|
208
|
-
grep -r "generate_external_identifier" --include="*.rb" .
|
|
209
|
-
# Replace with: derive_external_identifier
|
|
210
|
-
|
|
211
|
-
grep -r "generate_extid" --include="*.rb" .
|
|
212
|
-
# Remove usage - use derive_external_identifier instead
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### 2. Add HMAC Secret for VerifiableIdentifier
|
|
216
|
-
|
|
217
|
-
If you plan to use VerifiableIdentifier:
|
|
218
|
-
|
|
219
|
-
```ruby
|
|
220
|
-
# Generate secret
|
|
221
|
-
require 'securerandom'
|
|
222
|
-
secret = SecureRandom.hex(64)
|
|
223
|
-
|
|
224
|
-
# Add to your environment configuration
|
|
225
|
-
# .env, Rails credentials, or similar
|
|
226
|
-
VERIFIABLE_ID_HMAC_SECRET=your_generated_secret
|
|
227
|
-
|
|
228
|
-
# Verify configuration
|
|
229
|
-
puts ENV['VERIFIABLE_ID_HMAC_SECRET']&.length # Should be 128 characters
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
### 3. Update ExternalIdentifier Usage
|
|
233
|
-
|
|
234
|
-
Ensure proper dependency chain:
|
|
235
|
-
|
|
236
|
-
```ruby
|
|
237
|
-
class YourModel < Familia::Horreum
|
|
238
|
-
# Required: ObjectIdentifier must come before ExternalIdentifier
|
|
239
|
-
feature :object_identifier
|
|
240
|
-
feature :external_identifier
|
|
241
|
-
|
|
242
|
-
# Configure generator if needed
|
|
243
|
-
object_identifier_generator :uuid_v7
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
# Usage pattern
|
|
247
|
-
model = YourModel.new
|
|
248
|
-
model.generate_object_identifier # Generate objid first
|
|
249
|
-
extid = model.derive_external_identifier # Then derive external ID
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### 4. Review Security-Sensitive Code
|
|
253
|
-
|
|
254
|
-
Audit any code that processes identifiers from external sources:
|
|
255
|
-
|
|
256
|
-
```ruby
|
|
257
|
-
# Before: Potentially unsafe
|
|
258
|
-
def process_identifier(external_id)
|
|
259
|
-
# Could process forged identifiers
|
|
260
|
-
model = Model.find_by_external_id(external_id)
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# After: With verification
|
|
264
|
-
def process_identifier(verifiable_id)
|
|
265
|
-
# Verify identifier authenticity first
|
|
266
|
-
unless Model.verified_identifier?(verifiable_id)
|
|
267
|
-
raise SecurityError, "Invalid identifier"
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
original_id = Model.extract_identifier(verifiable_id)
|
|
271
|
-
model = Model.new(original_id)
|
|
272
|
-
end
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
## Breaking Changes
|
|
276
|
-
|
|
277
|
-
1. **`generate_extid` removed** - Use instance-level `derive_external_identifier` instead
|
|
278
|
-
2. **ExternalIdentifier validation** - Now raises `ExternalIdentifierError` instead of returning `nil` for models without objid
|
|
279
|
-
3. **Method names changed** - `generate_objid` → `generate_object_identifier`, `generate_external_identifier` → `derive_external_identifier`
|
|
280
|
-
|
|
281
|
-
## New Security Capabilities
|
|
282
|
-
|
|
283
|
-
1. **Cryptographic identifier verification** - Prevent forged IDs with HMAC signatures
|
|
284
|
-
2. **Scoped namespaces** - Isolate identifiers by tenant, environment, or role
|
|
285
|
-
3. **Provenance tracking** - Know which generator created each identifier
|
|
286
|
-
4. **Information leakage prevention** - External IDs no longer directly expose internal IDs
|
|
287
|
-
5. **Input validation** - Clear error messages for invalid operations
|
|
288
|
-
|
|
289
|
-
## Testing Your Migration
|
|
290
|
-
|
|
291
|
-
```ruby
|
|
292
|
-
# Test ObjectIdentifier changes
|
|
293
|
-
model = YourModel.new
|
|
294
|
-
objid = model.generate_object_identifier
|
|
295
|
-
extid = model.derive_external_identifier
|
|
296
|
-
puts "Generator: #{model.object_identifier_generator_type}"
|
|
297
|
-
|
|
298
|
-
# Test VerifiableIdentifier (if using)
|
|
299
|
-
vid = model.generate_verifiable_id
|
|
300
|
-
puts "Verifiable: #{YourModel.verified_identifier?(vid)}"
|
|
301
|
-
|
|
302
|
-
# Test scoped identifiers (if using)
|
|
303
|
-
scoped_vid = model.generate_verifiable_id(scope: 'production')
|
|
304
|
-
puts "Scoped valid: #{YourModel.verified_identifier?(scoped_vid, scope: 'production')}"
|
|
305
|
-
puts "Wrong scope: #{YourModel.verified_identifier?(scoped_vid, scope: 'development')}"
|
|
306
|
-
```
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Migrating Guide: v2.0.0-pre13
|
|
2
|
-
|
|
3
|
-
This version introduces the Feature Autoloading System for automatic discovery and loading of feature-specific configuration files, enabling cleaner separation between core model definitions and feature configurations.
|
|
4
|
-
|
|
5
|
-
## Feature Autoloading System
|
|
6
|
-
|
|
7
|
-
### What Changed
|
|
8
|
-
|
|
9
|
-
Features now automatically discover and load extension files from your project directories using conventional file naming patterns. This eliminates the need to configure features in your main model files.
|
|
10
|
-
|
|
11
|
-
### Basic Migration
|
|
12
|
-
|
|
13
|
-
#### Before
|
|
14
|
-
```ruby
|
|
15
|
-
# app/models/user.rb
|
|
16
|
-
class User < Familia::Horreum
|
|
17
|
-
field :name, :email, :password
|
|
18
|
-
|
|
19
|
-
feature :safe_dump
|
|
20
|
-
safe_dump_fields :name, :email # Configuration mixed with model
|
|
21
|
-
end
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
#### After
|
|
25
|
-
```ruby
|
|
26
|
-
# app/models/user.rb - Clean model definition
|
|
27
|
-
class User < Familia::Horreum
|
|
28
|
-
field :name, :email, :password
|
|
29
|
-
feature :safe_dump # Configuration auto-loaded
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# app/models/user/safe_dump_extensions.rb - Automatically discovered
|
|
33
|
-
class User
|
|
34
|
-
safe_dump_fields :name, :email
|
|
35
|
-
end
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### File Naming Convention
|
|
39
|
-
|
|
40
|
-
Extension files follow the pattern: `{model_name}/{feature_name}_*.rb`
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
app/models/
|
|
44
|
-
├── user.rb
|
|
45
|
-
├── user/
|
|
46
|
-
│ ├── safe_dump_extensions.rb # SafeDump configuration
|
|
47
|
-
│ └── expiration_config.rb # Expiration settings
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
### Migration Steps
|
|
51
|
-
|
|
52
|
-
1. **Create extension directories**: `mkdir -p app/models/user`
|
|
53
|
-
2. **Extract feature configuration** from main model files to separate extension files
|
|
54
|
-
3. **Verify autoloading**: Check that feature methods are available after migration
|
|
55
|
-
|
|
56
|
-
### Debugging
|
|
57
|
-
|
|
58
|
-
Enable debug output to troubleshoot autoloading:
|
|
59
|
-
```ruby
|
|
60
|
-
ENV['FAMILIA_DEBUG'] = '1' # Shows discovered and loaded files
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
Common issues:
|
|
64
|
-
- Files must follow `{feature_name}_*.rb` naming pattern
|
|
65
|
-
- Extension files should reopen the same class as your model
|
|
66
|
-
|
|
67
|
-
## Architecture
|
|
68
|
-
|
|
69
|
-
The Feature Autoloading System consists of two key components:
|
|
70
|
-
|
|
71
|
-
### Familia::Features::Autoloader
|
|
72
|
-
A utility module providing shared file loading functionality:
|
|
73
|
-
- Handles Dir.glob pattern matching and file loading
|
|
74
|
-
- Provides consistent debug logging across all autoloading scenarios
|
|
75
|
-
- Used by both feature-specific and general-purpose autoloading
|
|
76
|
-
|
|
77
|
-
### Familia::Features::Autoloadable
|
|
78
|
-
A mixin for feature modules that enables post-inclusion autoloading:
|
|
79
|
-
- Uses `Module.const_source_location` to find where user classes are defined
|
|
80
|
-
- Discovers extension files using conventional patterns relative to the user class location
|
|
81
|
-
- Integrates with the feature system's inclusion lifecycle
|
|
82
|
-
|
|
83
|
-
When you call `feature :safe_dump`, the SafeDump module (which includes Autoloadable) triggers post-inclusion autoloading that searches for `user/safe_dump_*.rb` files and loads them automatically.
|
|
84
|
-
|
|
85
|
-
## New Capabilities
|
|
86
|
-
|
|
87
|
-
1. **Automatic Extension Discovery**: No manual require statements needed
|
|
88
|
-
2. **Conventional File Organization**: Standard patterns for consistent project structure
|
|
89
|
-
3. **Feature Isolation**: Clean separation between core models and feature configurations
|
|
90
|
-
4. **Shared Autoloader Infrastructure**: Consistent loading behavior across all features
|
|
91
|
-
5. **Debug Support**: Built-in debugging for troubleshooting autoloading issues
|
|
92
|
-
|
|
93
|
-
## Breaking Changes
|
|
94
|
-
|
|
95
|
-
**None** - This release is fully backward compatible. Existing models and feature configurations continue to work without modification.
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Migrating Guide: v2.0.0-pre14
|
|
2
|
-
|
|
3
|
-
This version renames TimeUtils to TimeLiterals for semantic clarity and fixes an ExternalIdentifier bug.
|
|
4
|
-
|
|
5
|
-
## Breaking Change: TimeUtils → TimeLiterals
|
|
6
|
-
|
|
7
|
-
**Migration Required:**
|
|
8
|
-
|
|
9
|
-
Find and replace in your codebase:
|
|
10
|
-
```bash
|
|
11
|
-
# Find files to update
|
|
12
|
-
grep -r "using Familia::Refinements::TimeUtils" .
|
|
13
|
-
|
|
14
|
-
# Replace the import
|
|
15
|
-
sed -i 's/using Familia::Refinements::TimeUtils/using Familia::Refinements::TimeLiterals/g' *.rb
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Before:**
|
|
19
|
-
```ruby
|
|
20
|
-
using Familia::Refinements::TimeUtils
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**After:**
|
|
24
|
-
```ruby
|
|
25
|
-
using Familia::Refinements::TimeLiterals
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
All functionality remains identical - only the module name changed.
|
|
29
|
-
|
|
30
|
-
## Bug Fix: ExternalIdentifier
|
|
31
|
-
|
|
32
|
-
Fixed `NoMethodError` when using ExternalIdentifier by replacing incorrect `.del()` calls with `.remove_field()` in HashKey operations. This affected:
|
|
33
|
-
- Changing external identifier values
|
|
34
|
-
- Looking up objects by external ID
|
|
35
|
-
- Destroying objects with external identifiers
|
|
36
|
-
|
|
37
|
-
No migration needed - the fix is automatic.
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
# Migrating Guide: v2.0.0-pre18
|
|
2
|
-
|
|
3
|
-
This version completes the JSON serialization implementation by removing the string-as-is optimization and fixes encrypted field visibility in serialization.
|
|
4
|
-
|
|
5
|
-
## JSON Serialization for All Types
|
|
6
|
-
|
|
7
|
-
**What Changed:**
|
|
8
|
-
|
|
9
|
-
All field values (including strings) are now JSON-encoded during storage for consistent type preservation.
|
|
10
|
-
|
|
11
|
-
**Storage Format:**
|
|
12
|
-
|
|
13
|
-
```ruby
|
|
14
|
-
# Before (v2.0.0-pre14):
|
|
15
|
-
HGET user:123 name
|
|
16
|
-
"John Doe" # Plain string
|
|
17
|
-
|
|
18
|
-
# After (v2.0.0-pre18):
|
|
19
|
-
HGET user:123 name
|
|
20
|
-
"\"John Doe\"" # JSON-encoded string
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**Migration:**
|
|
24
|
-
|
|
25
|
-
No migration needed. The deserializer automatically handles:
|
|
26
|
-
- **New format**: `"\"value\""` → `"value"`
|
|
27
|
-
- **Legacy format**: `"value"` → `"value"`
|
|
28
|
-
|
|
29
|
-
**Why This Matters:**
|
|
30
|
-
|
|
31
|
-
Prevents data corruption for edge cases:
|
|
32
|
-
- Badge `"007"` stays `"007"` (not converted to integer `7`)
|
|
33
|
-
- String `"true"` stays `"true"` (not converted to boolean)
|
|
34
|
-
- String `"null"` stays `"null"` (not converted to `nil`)
|
|
35
|
-
|
|
36
|
-
## Encrypted Field Security Fix
|
|
37
|
-
|
|
38
|
-
**What Changed:**
|
|
39
|
-
|
|
40
|
-
Fields defined with `category: :encrypted` now correctly exclude encrypted data from `to_h()` output.
|
|
41
|
-
|
|
42
|
-
**Before:**
|
|
43
|
-
```ruby
|
|
44
|
-
field :secret, category: :encrypted
|
|
45
|
-
user.to_h # => {"id" => "123", "secret" => {...}} # ❌ Exposed!
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**After:**
|
|
49
|
-
```ruby
|
|
50
|
-
field :secret, category: :encrypted
|
|
51
|
-
user.to_h # => {"id" => "123"} # ✅ Secure
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Both `encrypted_field` and `field :name, category: :encrypted` now behave identically for security.
|
|
55
|
-
|
|
56
|
-
**Migration:**
|
|
57
|
-
|
|
58
|
-
No code changes needed. Review any code relying on encrypted fields appearing in `to_h()` output.
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
# Migrating Guide: v2.0.0-pre19
|
|
2
|
-
|
|
3
|
-
This version introduces significant improvements to Familia's database operations, making them more atomic, reliable, and consistent with Rails conventions. The changes include enhanced error handling, optimistic locking support, and breaking API changes.
|
|
4
|
-
|
|
5
|
-
## Breaking Changes
|
|
6
|
-
|
|
7
|
-
### Management.create → create!
|
|
8
|
-
|
|
9
|
-
**What Changed:**
|
|
10
|
-
|
|
11
|
-
The `create` class method has been renamed to `create!` to follow Rails conventions and indicate that it raises exceptions on failure.
|
|
12
|
-
|
|
13
|
-
**Before:**
|
|
14
|
-
```ruby
|
|
15
|
-
user = User.create(email: "test@example.com")
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**After:**
|
|
19
|
-
```ruby
|
|
20
|
-
user = User.create!(email: "test@example.com")
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
**Why This Matters:**
|
|
24
|
-
|
|
25
|
-
- Follows Rails naming conventions where `!` methods raise exceptions
|
|
26
|
-
- Makes it clear that `CreationError` will be raised if object already exists
|
|
27
|
-
- Prevents silent failures in object creation
|
|
28
|
-
|
|
29
|
-
### save_if_not_exists → save_if_not_exists!
|
|
30
|
-
|
|
31
|
-
**What Changed:**
|
|
32
|
-
|
|
33
|
-
The `save_if_not_exists` method has been renamed to `save_if_not_exists!` and now includes optimistic locking with automatic retry logic.
|
|
34
|
-
|
|
35
|
-
**Before:**
|
|
36
|
-
```ruby
|
|
37
|
-
success = user.save_if_not_exists
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
**After:**
|
|
41
|
-
```ruby
|
|
42
|
-
success = user.save_if_not_exists!
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
**Behavior Changes:**
|
|
46
|
-
- Now uses Redis WATCH/MULTI/EXEC for optimistic locking
|
|
47
|
-
- Automatically retries up to 3 times on `OptimisticLockError`
|
|
48
|
-
- Raises `RecordExistsError` if object already exists
|
|
49
|
-
- More atomic and thread-safe
|
|
50
|
-
|
|
51
|
-
## Enhanced Error Handling
|
|
52
|
-
|
|
53
|
-
### New Error Hierarchy
|
|
54
|
-
|
|
55
|
-
**What's New:**
|
|
56
|
-
|
|
57
|
-
A structured error hierarchy provides better error categorization:
|
|
58
|
-
|
|
59
|
-
```ruby
|
|
60
|
-
Familia::Problem # Base class
|
|
61
|
-
├── Familia::PersistenceError # Redis/database errors
|
|
62
|
-
│ ├── Familia::NonUniqueKey
|
|
63
|
-
│ ├── Familia::OptimisticLockError
|
|
64
|
-
│ ├── Familia::OperationModeError
|
|
65
|
-
│ ├── Familia::NoConnectionAvailable
|
|
66
|
-
│ ├── Familia::NotFound
|
|
67
|
-
│ └── Familia::NotConnected
|
|
68
|
-
└── Familia::HorreumError # Model-related errors
|
|
69
|
-
├── Familia::CreationError
|
|
70
|
-
├── Familia::NoIdentifier
|
|
71
|
-
├── Familia::FieldTypeError
|
|
72
|
-
├── Familia::AutoloadError
|
|
73
|
-
├── Familia::SerializerError
|
|
74
|
-
├── Familia::UnknownFieldError
|
|
75
|
-
└── Familia::NotDistinguishableError
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
**Migration:**
|
|
79
|
-
|
|
80
|
-
Update exception handling to use specific error classes:
|
|
81
|
-
|
|
82
|
-
```ruby
|
|
83
|
-
# Before
|
|
84
|
-
rescue Familia::Problem => e
|
|
85
|
-
# Handle all errors
|
|
86
|
-
|
|
87
|
-
# After - More granular handling
|
|
88
|
-
rescue Familia::CreationError => e
|
|
89
|
-
# Handle creation failures specifically
|
|
90
|
-
rescue Familia::OptimisticLockError => e
|
|
91
|
-
# Handle concurrent modification
|
|
92
|
-
rescue Familia::PersistenceError => e
|
|
93
|
-
# Handle database-related errors
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### New Exception Types
|
|
97
|
-
|
|
98
|
-
- **`CreationError`**: Raised when object creation fails (replaces generic errors)
|
|
99
|
-
- **`OptimisticLockError`**: Raised when WATCH fails due to concurrent modification
|
|
100
|
-
- **`UnknownFieldError`**: Raised when referencing non-existent fields
|
|
101
|
-
|
|
102
|
-
## Database Operation Improvements
|
|
103
|
-
|
|
104
|
-
### Atomic Save Operations
|
|
105
|
-
|
|
106
|
-
**What Changed:**
|
|
107
|
-
|
|
108
|
-
The `save` method now uses a single Redis transaction for complete atomicity:
|
|
109
|
-
|
|
110
|
-
```ruby
|
|
111
|
-
# All operations now happen atomically:
|
|
112
|
-
# 1. Save all fields (HMSET)
|
|
113
|
-
# 2. Set expiration (EXPIRE)
|
|
114
|
-
# 3. Update indexes
|
|
115
|
-
# 4. Add to instances collection
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
**Benefits:**
|
|
119
|
-
- Eliminates race conditions during save operations
|
|
120
|
-
- Ensures data consistency across related operations
|
|
121
|
-
- Better performance with fewer round trips
|
|
122
|
-
|
|
123
|
-
### Enhanced Timestamp Precision
|
|
124
|
-
|
|
125
|
-
**What Changed:**
|
|
126
|
-
|
|
127
|
-
Created/updated timestamps now use float values instead of integers for higher precision:
|
|
128
|
-
|
|
129
|
-
**Before:**
|
|
130
|
-
```ruby
|
|
131
|
-
user.created # => 1697234567 (integer seconds)
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**After:**
|
|
135
|
-
```ruby
|
|
136
|
-
user.created # => 1697234567.123 (float with milliseconds)
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
**Migration:**
|
|
140
|
-
|
|
141
|
-
No code changes needed. Existing integer timestamps continue to work.
|
|
142
|
-
|
|
143
|
-
## Redis Command Enhancements
|
|
144
|
-
|
|
145
|
-
### New Commands Available
|
|
146
|
-
|
|
147
|
-
Added support for optimistic locking commands:
|
|
148
|
-
- `watch(key)` - Watch key for changes
|
|
149
|
-
- `unwatch()` - Remove all watches
|
|
150
|
-
- `discard()` - Discard queued commands
|
|
151
|
-
|
|
152
|
-
### Improved Command Logging
|
|
153
|
-
|
|
154
|
-
Database command logging now includes:
|
|
155
|
-
- Structured format for better readability
|
|
156
|
-
- Pipelined operation tracking
|
|
157
|
-
- Transaction boundary markers
|
|
158
|
-
- Command timing information
|
|
159
|
-
|
|
160
|
-
## Terminology Updates
|
|
161
|
-
|
|
162
|
-
**What Changed:**
|
|
163
|
-
|
|
164
|
-
Standardized on "pipelined" terminology throughout (previously mixed "pipeline"/"pipelined").
|
|
165
|
-
|
|
166
|
-
**Files Affected:**
|
|
167
|
-
- Method names now consistently use "pipelined"
|
|
168
|
-
- Documentation updated to match Redis terminology
|
|
169
|
-
- Log messages standardized
|
|
170
|
-
|
|
171
|
-
**Migration:**
|
|
172
|
-
|
|
173
|
-
No code changes needed - this was an internal consistency improvement.
|
|
174
|
-
|
|
175
|
-
## Recommended Actions
|
|
176
|
-
|
|
177
|
-
1. **Update method calls:**
|
|
178
|
-
```ruby
|
|
179
|
-
# Replace all instances
|
|
180
|
-
Model.create(...) → Model.create!(...)
|
|
181
|
-
obj.save_if_not_exists → obj.save_if_not_exists!
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
2. **Review error handling:**
|
|
185
|
-
```ruby
|
|
186
|
-
# Consider more specific error handling
|
|
187
|
-
rescue Familia::CreationError
|
|
188
|
-
rescue Familia::OptimisticLockError
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
3. **Test concurrent operations:**
|
|
192
|
-
- The new optimistic locking provides better concurrency handling
|
|
193
|
-
- Verify your application handles `OptimisticLockError` appropriately
|
|
194
|
-
|
|
195
|
-
4. **Review logging:**
|
|
196
|
-
- Enhanced database command logging may affect log volume
|
|
197
|
-
- Adjust log levels if needed for production environments
|