familia 2.0.0.pre10 → 2.0.0.pre13
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/.rubocop_todo.yml +2 -3
- data/CHANGELOG.rst +507 -0
- data/CLAUDE.md +5 -55
- data/Gemfile +1 -6
- data/Gemfile.lock +13 -7
- data/changelog.d/README.md +45 -34
- data/changelog.d/scriv.ini +5 -0
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +1 -1
- data/docs/archive/FAMILIA_UPDATE.md +1 -1
- data/docs/archive/README.md +15 -19
- data/docs/guides/Feature-System-Autoloading.md +228 -0
- data/docs/guides/Home.md +1 -1
- data/docs/guides/Implementation-Guide.md +1 -1
- data/docs/guides/relationships-methods.md +1 -1
- data/docs/guides/time-utilities.md +221 -0
- data/docs/migrating/.gitignore +2 -0
- data/docs/migrating/v2.0.0-pre.md +84 -0
- data/docs/migrating/v2.0.0-pre11.md +253 -0
- data/docs/migrating/v2.0.0-pre12.md +306 -0
- data/docs/migrating/v2.0.0-pre13.md +329 -0
- data/docs/migrating/v2.0.0-pre5.md +110 -0
- data/docs/migrating/v2.0.0-pre6.md +154 -0
- data/docs/migrating/v2.0.0-pre7.md +222 -0
- data/docs/overview.md +6 -7
- data/{examples/redis_command_validation_example.rb → docs/reference/auditing_database_commands.rb} +29 -32
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +6 -0
- data/examples/autoloader/mega_customer.rb +17 -0
- data/examples/{bit_encoding_integration.rb → permissions.rb} +30 -27
- data/examples/{relationships_basic.rb → relationships.rb} +2 -3
- data/examples/safe_dump.rb +281 -0
- data/familia.gemspec +5 -4
- data/lib/familia/autoloader.rb +53 -0
- data/lib/familia/base.rb +57 -0
- data/lib/familia/data_type.rb +4 -0
- data/lib/familia/encryption/encrypted_data.rb +4 -4
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/{encryption_request_cache.rb → encryption/request_cache.rb} +1 -1
- data/lib/familia/encryption.rb +1 -1
- data/lib/familia/errors.rb +5 -0
- data/lib/familia/features/autoloadable.rb +113 -0
- data/lib/familia/features/encrypted_fields/concealed_string.rb +4 -2
- data/lib/familia/features/expiration.rb +4 -0
- data/lib/familia/features/external_identifier.rb +310 -0
- data/lib/familia/features/object_identifier.rb +307 -0
- data/lib/familia/features/quantization.rb +5 -0
- data/lib/familia/features/safe_dump.rb +74 -73
- data/lib/familia/features.rb +109 -17
- data/lib/familia/field_type.rb +2 -0
- data/lib/familia/horreum/core/serialization.rb +3 -3
- data/lib/familia/horreum/subclass/definition.rb +50 -7
- data/lib/familia/horreum.rb +2 -0
- data/lib/familia/json_serializer.rb +70 -0
- data/lib/familia/logging.rb +12 -10
- data/lib/familia/refinements/logger_trace.rb +57 -0
- data/lib/familia/refinements/snake_case.rb +40 -0
- data/lib/familia/refinements/time_utils.rb +248 -0
- data/lib/familia/refinements.rb +3 -49
- data/lib/familia/secure_identifier.rb +51 -75
- data/lib/familia/utils.rb +2 -0
- data/lib/familia/validation/{test_helpers.rb → validation_helpers.rb} +2 -2
- data/lib/familia/validation.rb +1 -1
- data/lib/familia/verifiable_identifier.rb +162 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +15 -2
- data/try/core/autoloader_try.rb +112 -0
- data/try/core/extensions_try.rb +38 -21
- data/try/core/familia_extended_try.rb +4 -3
- data/try/core/secure_identifier_try.rb +47 -18
- data/try/core/time_utils_try.rb +130 -0
- data/try/core/verifiable_identifier_try.rb +171 -0
- data/try/data_types/datatype_base_try.rb +3 -2
- data/try/features/autoloadable/autoloadable_try.rb +61 -0
- data/try/features/encrypted_fields/concealed_string_core_try.rb +8 -3
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +59 -17
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +36 -12
- data/try/features/{external_identifiers/external_identifiers_try.rb → external_identifier/external_identifier_try.rb} +25 -28
- data/try/features/feature_improvements_try.rb +127 -0
- data/try/features/{object_identifiers/object_identifiers_integration_try.rb → object_identifier/object_identifier_integration_try.rb} +28 -30
- data/try/features/{object_identifiers/object_identifiers_try.rb → object_identifier/object_identifier_try.rb} +13 -13
- data/try/features/real_feature_integration_try.rb +8 -7
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +111 -0
- data/try/features/safe_dump/safe_dump_try.rb +8 -9
- data/try/helpers/test_helpers.rb +41 -17
- data/try/integration/cross_component_try.rb +3 -1
- metadata +61 -26
- data/CHANGELOG.md +0 -184
- data/changelog.d/fragments/.keep +0 -0
- data/changelog.d/template.md.j2 +0 -29
- data/lib/familia/core_ext.rb +0 -135
- data/lib/familia/features/external_identifiers/external_identifier_field_type.rb +0 -120
- data/lib/familia/features/external_identifiers.rb +0 -111
- data/lib/familia/features/object_identifiers/object_identifier_field_type.rb +0 -91
- data/lib/familia/features/object_identifiers.rb +0 -194
- data/setup.cfg +0 -12
@@ -0,0 +1,306 @@
|
|
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: Set 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
|
+
# Set 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
|
+
```
|
@@ -0,0 +1,329 @@
|
|
1
|
+
# Migrating Guide: v2.0.0-pre13
|
2
|
+
|
3
|
+
This version introduces significant improvements to Familia's feature system, making it easier to organize and use features across complex projects through automatic discovery and loading of feature-specific configuration files.
|
4
|
+
|
5
|
+
## Feature-Specific Autoloading System
|
6
|
+
|
7
|
+
### Overview
|
8
|
+
|
9
|
+
The new autoloading system allows features to automatically discover and load extension files from your project directories. When you include a feature in your model, Familia now searches for configuration files using conventional patterns, enabling clean separation between core model definitions and feature-specific configurations.
|
10
|
+
|
11
|
+
### Basic Usage
|
12
|
+
|
13
|
+
#### Before (Manual Configuration)
|
14
|
+
```ruby
|
15
|
+
# app/models/user.rb
|
16
|
+
class User < Familia::Horreum
|
17
|
+
field :name, :email, :password
|
18
|
+
|
19
|
+
feature :safe_dump
|
20
|
+
# All configuration had to be done in the same file
|
21
|
+
safe_dump_fields :name, :email # password excluded for security
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
#### After (Automatic Discovery)
|
26
|
+
```ruby
|
27
|
+
# app/models/user.rb - Clean model definition
|
28
|
+
class User < Familia::Horreum
|
29
|
+
field :name, :email, :password
|
30
|
+
feature :safe_dump # ← Triggers autoloading
|
31
|
+
end
|
32
|
+
|
33
|
+
# app/models/user/safe_dump_extensions.rb - Automatically loaded
|
34
|
+
class User
|
35
|
+
safe_dump_fields :name, :email # password excluded for security
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
### File Naming Conventions
|
40
|
+
|
41
|
+
The autoloading system follows these patterns for discovering extension files:
|
42
|
+
|
43
|
+
#### Pattern: `{model_name}/{feature_name}_*.rb`
|
44
|
+
|
45
|
+
**Example Structures:**
|
46
|
+
```
|
47
|
+
app/models/
|
48
|
+
├── user.rb # Main model
|
49
|
+
├── user/
|
50
|
+
│ ├── safe_dump_extensions.rb # SafeDump configuration
|
51
|
+
│ ├── safe_dump_custom.rb # Additional SafeDump setup
|
52
|
+
│ └── relationships_config.rb # Relationships configuration
|
53
|
+
├── product.rb # Another model
|
54
|
+
└── product/
|
55
|
+
├── safe_dump_fields.rb # Product's SafeDump config
|
56
|
+
└── expiration_settings.rb # Expiration configuration
|
57
|
+
```
|
58
|
+
|
59
|
+
#### Supported Patterns
|
60
|
+
- `safe_dump_extensions.rb`
|
61
|
+
- `safe_dump_*.rb` (any filename starting with the feature name)
|
62
|
+
- `expiration_config.rb`
|
63
|
+
- `relationships_setup.rb`
|
64
|
+
|
65
|
+
### Advanced Configuration Examples
|
66
|
+
|
67
|
+
#### Complex SafeDump Setup
|
68
|
+
```ruby
|
69
|
+
# app/models/customer.rb
|
70
|
+
class Customer < Familia::Horreum
|
71
|
+
field :first_name, :last_name, :email, :phone
|
72
|
+
field :credit_card_number, :ssn, :internal_notes
|
73
|
+
|
74
|
+
feature :safe_dump
|
75
|
+
end
|
76
|
+
|
77
|
+
# app/models/customer/safe_dump_configuration.rb
|
78
|
+
class Customer
|
79
|
+
# Define which fields are safe for API responses
|
80
|
+
safe_dump_fields :first_name, :last_name, :email
|
81
|
+
|
82
|
+
# Custom serialization for specific fields
|
83
|
+
def safe_dump_email
|
84
|
+
email&.downcase
|
85
|
+
end
|
86
|
+
|
87
|
+
# Computed fields for API
|
88
|
+
def safe_dump_full_name
|
89
|
+
"#{first_name} #{last_name}".strip
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
#### Multi-Feature Organization
|
95
|
+
```ruby
|
96
|
+
# app/models/session.rb
|
97
|
+
class Session < Familia::Horreum
|
98
|
+
field :user_id, :token, :ip_address, :user_agent
|
99
|
+
|
100
|
+
feature :safe_dump
|
101
|
+
feature :expiration
|
102
|
+
end
|
103
|
+
|
104
|
+
# app/models/session/safe_dump_api.rb
|
105
|
+
class Session
|
106
|
+
safe_dump_fields :user_id, :ip_address
|
107
|
+
# token excluded for security
|
108
|
+
end
|
109
|
+
|
110
|
+
# app/models/session/expiration_policy.rb
|
111
|
+
class Session
|
112
|
+
# Sessions expire after 24 hours
|
113
|
+
def self.default_ttl
|
114
|
+
24 * 60 * 60
|
115
|
+
end
|
116
|
+
|
117
|
+
# Cascade expiration to related data
|
118
|
+
cascade_expiration_to :user_sessions
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
### Consolidated Autoloader Architecture
|
123
|
+
|
124
|
+
#### Familia::Autoloader
|
125
|
+
|
126
|
+
The new `Familia::Autoloader` class provides a shared utility for consistent file loading patterns across the framework:
|
127
|
+
|
128
|
+
```ruby
|
129
|
+
# Internal usage example (you typically won't call this directly)
|
130
|
+
autoloader = Familia::Autoloader.new(
|
131
|
+
base_class: User,
|
132
|
+
feature_name: :safe_dump,
|
133
|
+
search_patterns: ['user/safe_dump_*.rb']
|
134
|
+
)
|
135
|
+
|
136
|
+
# Discovers and loads matching files
|
137
|
+
autoloader.discover_and_load_extensions
|
138
|
+
```
|
139
|
+
|
140
|
+
#### Autoloading Strategies
|
141
|
+
|
142
|
+
The autoloader supports multiple discovery strategies:
|
143
|
+
|
144
|
+
1. **Directory-based**: Search in conventional directories
|
145
|
+
2. **Pattern-based**: Use glob patterns for flexible matching
|
146
|
+
3. **Explicit paths**: Load specific files when found
|
147
|
+
|
148
|
+
### How Autoloading Works
|
149
|
+
|
150
|
+
#### Discovery Process
|
151
|
+
|
152
|
+
1. **Feature Activation**: When `feature :safe_dump` is called
|
153
|
+
2. **Path Resolution**: Autoloader determines search paths based on model location
|
154
|
+
3. **Pattern Matching**: Searches for files matching `{model_name}/{feature_name}_*.rb`
|
155
|
+
4. **File Loading**: Loads discovered files in alphabetical order
|
156
|
+
5. **Extension Application**: Feature-specific methods become available
|
157
|
+
|
158
|
+
#### Search Locations
|
159
|
+
|
160
|
+
The autoloader searches in these locations (in order):
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
# If your model is in app/models/user.rb, it searches:
|
164
|
+
[
|
165
|
+
'app/models/user/', # Same directory as model
|
166
|
+
'lib/user/', # Lib directory
|
167
|
+
'config/models/user/', # Config directory
|
168
|
+
'./user/' # Current directory
|
169
|
+
]
|
170
|
+
```
|
171
|
+
|
172
|
+
### Migration Steps
|
173
|
+
|
174
|
+
#### 1. Reorganize Existing Models
|
175
|
+
|
176
|
+
Move feature-specific configuration to separate files:
|
177
|
+
|
178
|
+
```bash
|
179
|
+
# Create directories for feature configurations
|
180
|
+
mkdir -p app/models/user
|
181
|
+
mkdir -p app/models/product
|
182
|
+
mkdir -p app/models/order
|
183
|
+
|
184
|
+
# Move configurations
|
185
|
+
# From app/models/user.rb, extract safe_dump configuration to:
|
186
|
+
# app/models/user/safe_dump_extensions.rb
|
187
|
+
```
|
188
|
+
|
189
|
+
#### 2. Update Model Files
|
190
|
+
|
191
|
+
Clean up your main model files:
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
# Before: Everything in one file
|
195
|
+
class User < Familia::Horreum
|
196
|
+
field :name, :email, :password, :role
|
197
|
+
|
198
|
+
feature :safe_dump
|
199
|
+
safe_dump_fields :name, :email
|
200
|
+
|
201
|
+
feature :expiration
|
202
|
+
def self.default_ttl
|
203
|
+
86400
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# After: Clean separation
|
208
|
+
class User < Familia::Horreum
|
209
|
+
field :name, :email, :password, :role
|
210
|
+
|
211
|
+
feature :safe_dump # Configuration auto-loaded
|
212
|
+
feature :expiration # Configuration auto-loaded
|
213
|
+
end
|
214
|
+
```
|
215
|
+
|
216
|
+
#### 3. Create Extension Files
|
217
|
+
|
218
|
+
Set up your feature-specific configurations:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
# app/models/user/safe_dump_extensions.rb
|
222
|
+
class User
|
223
|
+
safe_dump_fields :name, :email
|
224
|
+
# password and role excluded for security
|
225
|
+
end
|
226
|
+
|
227
|
+
# app/models/user/expiration_config.rb
|
228
|
+
class User
|
229
|
+
def self.default_ttl
|
230
|
+
86400 # 24 hours
|
231
|
+
end
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
### Benefits of the New System
|
236
|
+
|
237
|
+
#### Code Organization
|
238
|
+
- **Separation of Concerns**: Feature logic separated from core model definition
|
239
|
+
- **Maintainability**: Easier to find and modify feature-specific code
|
240
|
+
- **Readability**: Core models are cleaner and more focused
|
241
|
+
|
242
|
+
#### Team Development
|
243
|
+
- **Reduced Conflicts**: Multiple developers can work on different features without merge conflicts
|
244
|
+
- **Feature Ownership**: Clear boundaries for feature-specific code
|
245
|
+
- **Testing**: Easier to test features in isolation
|
246
|
+
|
247
|
+
#### Scalability
|
248
|
+
- **Large Models**: Complex models remain manageable
|
249
|
+
- **Feature Growth**: New features can be added without bloating main model files
|
250
|
+
- **Refactoring**: Easier to extract and reorganize feature code
|
251
|
+
|
252
|
+
### Debugging Autoloading
|
253
|
+
|
254
|
+
#### Enable Debug Output
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
# In your application initialization
|
258
|
+
ENV['FAMILIA_DEBUG'] = '1'
|
259
|
+
|
260
|
+
# Or programmatically
|
261
|
+
Familia::Features::Autoloadable.debug = true
|
262
|
+
```
|
263
|
+
|
264
|
+
#### Debug Output Example
|
265
|
+
```
|
266
|
+
[Familia::Autoloader] Searching for User safe_dump extensions...
|
267
|
+
[Familia::Autoloader] Found: app/models/user/safe_dump_extensions.rb
|
268
|
+
[Familia::Autoloader] Loading: app/models/user/safe_dump_extensions.rb
|
269
|
+
[Familia::Autoloader] SafeDump extension loaded successfully for User
|
270
|
+
```
|
271
|
+
|
272
|
+
#### Troubleshooting
|
273
|
+
|
274
|
+
**Common Issues:**
|
275
|
+
|
276
|
+
1. **File Not Found**: Ensure file naming follows the `{feature_name}_*.rb` pattern
|
277
|
+
2. **Load Order**: Files are loaded alphabetically; prefix with numbers if order matters
|
278
|
+
3. **Class Scope**: Extension files should reopen the same class as your model
|
279
|
+
|
280
|
+
**Validation:**
|
281
|
+
```ruby
|
282
|
+
# Check if autoloading worked
|
283
|
+
User.respond_to?(:safe_dump_field_names) # => true
|
284
|
+
User.safe_dump_field_names # => [:name, :email]
|
285
|
+
```
|
286
|
+
|
287
|
+
### Backward Compatibility
|
288
|
+
|
289
|
+
The autoloading system is fully backward compatible:
|
290
|
+
|
291
|
+
- **Existing Models**: Continue to work without changes
|
292
|
+
- **Manual Configuration**: Still supported alongside autoloading
|
293
|
+
- **Gradual Migration**: You can migrate models one at a time
|
294
|
+
|
295
|
+
### Performance Considerations
|
296
|
+
|
297
|
+
- **Load Time**: Files are loaded once during feature activation
|
298
|
+
- **Memory Usage**: No additional memory overhead after loading
|
299
|
+
- **Caching**: Discovered files are cached to avoid repeated filesystem scans
|
300
|
+
|
301
|
+
### Testing Your Migration
|
302
|
+
|
303
|
+
```ruby
|
304
|
+
# Test that autoloading is working
|
305
|
+
class TestUser < Familia::Horreum
|
306
|
+
field :name, :email
|
307
|
+
feature :safe_dump
|
308
|
+
end
|
309
|
+
|
310
|
+
# Verify extension methods are available
|
311
|
+
puts TestUser.respond_to?(:safe_dump_field_names)
|
312
|
+
puts TestUser.safe_dump_field_names.inspect
|
313
|
+
|
314
|
+
# Test instance methods
|
315
|
+
user = TestUser.new(name: "John", email: "john@example.com")
|
316
|
+
puts user.safe_dump.inspect
|
317
|
+
```
|
318
|
+
|
319
|
+
## New Capabilities
|
320
|
+
|
321
|
+
1. **Automatic Extension Discovery**: No manual require statements needed
|
322
|
+
2. **Conventional File Organization**: Standard patterns for consistent project structure
|
323
|
+
3. **Feature Isolation**: Clean separation between core models and feature configurations
|
324
|
+
4. **Shared Autoloader Infrastructure**: Consistent loading behavior across all features
|
325
|
+
5. **Debug Support**: Built-in debugging for troubleshooting autoloading issues
|
326
|
+
|
327
|
+
## Breaking Changes
|
328
|
+
|
329
|
+
**None** - This release is fully backward compatible. Existing models and feature configurations continue to work without modification.
|