familia 2.0.0.pre5 → 2.0.0.pre6
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/CLAUDE.md +8 -5
- data/Gemfile +1 -1
- data/Gemfile.lock +4 -3
- data/docs/wiki/API-Reference.md +95 -18
- data/docs/wiki/Connection-Pooling-Guide.md +437 -0
- data/docs/wiki/Encrypted-Fields-Overview.md +40 -3
- data/docs/wiki/Expiration-Feature-Guide.md +596 -0
- data/docs/wiki/Feature-System-Guide.md +600 -0
- data/docs/wiki/Features-System-Developer-Guide.md +892 -0
- data/docs/wiki/Field-System-Guide.md +784 -0
- data/docs/wiki/Home.md +72 -15
- data/docs/wiki/Implementation-Guide.md +126 -33
- data/docs/wiki/Quantization-Feature-Guide.md +721 -0
- data/docs/wiki/RelatableObjects-Guide.md +563 -0
- data/docs/wiki/Security-Model.md +65 -25
- data/docs/wiki/Transient-Fields-Guide.md +280 -0
- data/lib/familia/base.rb +1 -1
- data/lib/familia/data_type/types/counter.rb +38 -0
- data/lib/familia/data_type/types/hashkey.rb +18 -0
- data/lib/familia/data_type/types/lock.rb +43 -0
- data/lib/familia/data_type/types/string.rb +9 -2
- data/lib/familia/data_type.rb +2 -2
- data/lib/familia/encryption/encrypted_data.rb +137 -0
- data/lib/familia/encryption/manager.rb +21 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +20 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +20 -0
- data/lib/familia/encryption.rb +1 -1
- data/lib/familia/errors.rb +17 -3
- data/lib/familia/features/encrypted_fields/concealed_string.rb +295 -0
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +94 -26
- data/lib/familia/features/expiration.rb +1 -1
- data/lib/familia/features/quantization.rb +1 -1
- data/lib/familia/features/safe_dump.rb +1 -1
- data/lib/familia/features/transient_fields/redacted_string.rb +1 -1
- data/lib/familia/features/transient_fields.rb +1 -1
- data/lib/familia/field_type.rb +5 -2
- data/lib/familia/horreum/{connection.rb → core/connection.rb} +2 -8
- data/lib/familia/horreum/{database_commands.rb → core/database_commands.rb} +14 -3
- data/lib/familia/horreum/core/serialization.rb +535 -0
- data/lib/familia/horreum/{utils.rb → core/utils.rb} +0 -2
- data/lib/familia/horreum/core.rb +21 -0
- data/lib/familia/horreum/{settings.rb → shared/settings.rb} +0 -2
- data/lib/familia/horreum/{definition_methods.rb → subclass/definition.rb} +44 -28
- data/lib/familia/horreum/{management_methods.rb → subclass/management.rb} +9 -8
- data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
- data/lib/familia/horreum.rb +17 -17
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +1 -1
- data/try/core/create_method_try.rb +240 -0
- data/try/core/database_consistency_try.rb +299 -0
- data/try/core/errors_try.rb +25 -4
- data/try/core/familia_try.rb +1 -1
- data/try/core/persistence_operations_try.rb +297 -0
- data/try/data_types/counter_try.rb +93 -0
- data/try/data_types/lock_try.rb +133 -0
- data/try/debugging/debug_aad_process.rb +82 -0
- data/try/debugging/debug_concealed_internal.rb +59 -0
- data/try/debugging/debug_concealed_reveal.rb +61 -0
- data/try/debugging/debug_context_aad.rb +68 -0
- data/try/debugging/debug_context_simple.rb +80 -0
- data/try/debugging/debug_cross_context.rb +62 -0
- data/try/debugging/debug_database_load.rb +64 -0
- data/try/debugging/debug_encrypted_json_check.rb +53 -0
- data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
- data/try/debugging/debug_exists_lifecycle.rb +54 -0
- data/try/debugging/debug_field_decrypt.rb +74 -0
- data/try/debugging/debug_fresh_cross_context.rb +73 -0
- data/try/debugging/debug_load_path.rb +66 -0
- data/try/debugging/debug_method_definition.rb +46 -0
- data/try/debugging/debug_method_resolution.rb +41 -0
- data/try/debugging/debug_minimal.rb +24 -0
- data/try/debugging/debug_provider.rb +68 -0
- data/try/debugging/debug_secure_behavior.rb +73 -0
- data/try/debugging/debug_string_class.rb +46 -0
- data/try/debugging/debug_test.rb +46 -0
- data/try/debugging/debug_test_design.rb +80 -0
- data/try/encryption/encryption_core_try.rb +3 -3
- data/try/features/encrypted_fields_core_try.rb +19 -11
- data/try/features/encrypted_fields_integration_try.rb +66 -70
- data/try/features/encrypted_fields_no_cache_security_try.rb +22 -8
- data/try/features/encrypted_fields_security_try.rb +151 -144
- data/try/features/encryption_fields/aad_protection_try.rb +108 -23
- data/try/features/encryption_fields/concealed_string_core_try.rb +250 -0
- data/try/features/encryption_fields/context_isolation_try.rb +29 -8
- data/try/features/encryption_fields/error_conditions_try.rb +6 -6
- data/try/features/encryption_fields/fresh_key_derivation_try.rb +20 -14
- data/try/features/encryption_fields/fresh_key_try.rb +27 -22
- data/try/features/encryption_fields/key_rotation_try.rb +16 -10
- data/try/features/encryption_fields/nonce_uniqueness_try.rb +15 -13
- data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
- data/try/features/encryption_fields/thread_safety_try.rb +6 -6
- data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields_integration_try.rb +1 -1
- data/try/helpers/test_helpers.rb +25 -0
- data/try/horreum/enhanced_conflict_handling_try.rb +1 -1
- data/try/horreum/initialization_try.rb +1 -1
- data/try/horreum/relations_try.rb +1 -1
- data/try/horreum/serialization_persistent_fields_try.rb +8 -8
- data/try/horreum/serialization_try.rb +39 -4
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +1 -1
- metadata +51 -10
- data/TEST_COVERAGE.md +0 -40
- data/lib/familia/horreum/serialization.rb +0 -473
@@ -0,0 +1,600 @@
|
|
1
|
+
# Feature System Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
Familia's feature system provides a modular architecture for extending Horreum classes with reusable functionality. Features are self-contained modules that can be mixed into classes with dependency management, conflict resolution, and automatic registration.
|
6
|
+
|
7
|
+
## Core Concepts
|
8
|
+
|
9
|
+
### Feature Architecture
|
10
|
+
|
11
|
+
The feature system consists of several key components:
|
12
|
+
|
13
|
+
1. **Feature Modules**: Self-contained functionality modules
|
14
|
+
2. **Registration System**: Automatic feature discovery and registration
|
15
|
+
3. **Dependency Management**: Explicit feature dependencies
|
16
|
+
4. **Conflict Resolution**: Handling method name conflicts
|
17
|
+
5. **Category-based Fields**: Special field types for different purposes
|
18
|
+
|
19
|
+
### Feature Lifecycle
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# 1. Feature definition and registration (automatic)
|
23
|
+
class MyFeature
|
24
|
+
def self.included(base)
|
25
|
+
base.extend ClassMethods
|
26
|
+
base.prepend InstanceMethods
|
27
|
+
end
|
28
|
+
|
29
|
+
# Self-register with Familia
|
30
|
+
Familia::Base.add_feature self, :my_feature, depends_on: [:other_feature]
|
31
|
+
end
|
32
|
+
|
33
|
+
# 2. Feature activation in classes
|
34
|
+
class Customer < Familia::Horreum
|
35
|
+
feature :my_feature # Validates, checks dependencies, includes module
|
36
|
+
end
|
37
|
+
|
38
|
+
# 3. Runtime usage
|
39
|
+
customer = Customer.new
|
40
|
+
customer.my_feature_method # Available after feature inclusion
|
41
|
+
```
|
42
|
+
|
43
|
+
## Built-in Features
|
44
|
+
|
45
|
+
### Core Features
|
46
|
+
|
47
|
+
#### Expiration
|
48
|
+
```ruby
|
49
|
+
class Session < Familia::Horreum
|
50
|
+
feature :expiration
|
51
|
+
default_expiration 1.hour
|
52
|
+
|
53
|
+
field :user_id, :data
|
54
|
+
end
|
55
|
+
|
56
|
+
session = Session.new(user_id: 123)
|
57
|
+
session.update_expiration(30.minutes) # Custom TTL
|
58
|
+
session.ttl # Check remaining time
|
59
|
+
```
|
60
|
+
|
61
|
+
#### SafeDump
|
62
|
+
```ruby
|
63
|
+
class Customer < Familia::Horreum
|
64
|
+
feature :safe_dump
|
65
|
+
|
66
|
+
field :name, :email
|
67
|
+
field :ssn # Sensitive field
|
68
|
+
field :password # Sensitive field
|
69
|
+
|
70
|
+
# Whitelist fields for API responses
|
71
|
+
safe_dump_fields :name, :email # Excludes ssn, password
|
72
|
+
end
|
73
|
+
|
74
|
+
customer.safe_dump # => { name: "John", email: "john@example.com" }
|
75
|
+
customer.dump # => { name: "John", email: "john@example.com", ssn: "123-45-6789", password: "secret" }
|
76
|
+
```
|
77
|
+
|
78
|
+
#### Encrypted Fields
|
79
|
+
```ruby
|
80
|
+
class Vault < Familia::Horreum
|
81
|
+
feature :encrypted_fields
|
82
|
+
|
83
|
+
field :name # Regular field
|
84
|
+
encrypted_field :secret_key # Encrypted storage
|
85
|
+
encrypted_field :api_token # Another encrypted field
|
86
|
+
end
|
87
|
+
|
88
|
+
vault = Vault.new(secret_key: "super-secret")
|
89
|
+
vault.save
|
90
|
+
# secret_key is encrypted in Redis, decrypted on access
|
91
|
+
```
|
92
|
+
|
93
|
+
#### Transient Fields
|
94
|
+
```ruby
|
95
|
+
class ApiClient < Familia::Horreum
|
96
|
+
feature :transient_fields
|
97
|
+
|
98
|
+
field :endpoint # Persistent field
|
99
|
+
transient_field :auth_token # Runtime only, RedactedString
|
100
|
+
end
|
101
|
+
|
102
|
+
client = ApiClient.new(auth_token: ENV['API_TOKEN'])
|
103
|
+
client.auth_token.expose { |token| make_api_call(token) }
|
104
|
+
client.auth_token.clear! # Explicit cleanup
|
105
|
+
```
|
106
|
+
|
107
|
+
#### Quantization
|
108
|
+
```ruby
|
109
|
+
class Metric < Familia::Horreum
|
110
|
+
feature :quantization
|
111
|
+
|
112
|
+
field :value
|
113
|
+
quantized_field :hourly_stats, interval: 1.hour
|
114
|
+
quantized_field :daily_stats, interval: 1.day
|
115
|
+
end
|
116
|
+
|
117
|
+
# Automatically buckets data by time intervals
|
118
|
+
metric = Metric.new(value: 42)
|
119
|
+
metric.hourly_stats # Bucketed by hour
|
120
|
+
metric.daily_stats # Bucketed by day
|
121
|
+
```
|
122
|
+
|
123
|
+
## Creating Custom Features
|
124
|
+
|
125
|
+
### Basic Feature Structure
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
module Familia
|
129
|
+
module Features
|
130
|
+
module MyCustomFeature
|
131
|
+
def self.included(base)
|
132
|
+
Familia.trace :LOADED, self, base, caller(1..1) if Familia.debug?
|
133
|
+
base.extend ClassMethods
|
134
|
+
base.prepend InstanceMethods # Use prepend for method interception
|
135
|
+
end
|
136
|
+
|
137
|
+
module ClassMethods
|
138
|
+
def custom_class_method
|
139
|
+
"Available on #{self} class"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
module InstanceMethods
|
144
|
+
def custom_instance_method
|
145
|
+
"Available on #{self.class} instances"
|
146
|
+
end
|
147
|
+
|
148
|
+
# Intercept field access (if needed)
|
149
|
+
def field_value=(value)
|
150
|
+
# Custom processing before field assignment
|
151
|
+
processed_value = process_value(value)
|
152
|
+
super(processed_value) # Call original field setter
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Register the feature
|
157
|
+
Familia::Base.add_feature self, :my_custom_feature
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
### Advanced Feature with Dependencies
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
module Familia
|
167
|
+
module Features
|
168
|
+
module AdvancedAudit
|
169
|
+
def self.included(base)
|
170
|
+
base.extend ClassMethods
|
171
|
+
base.prepend InstanceMethods
|
172
|
+
|
173
|
+
# Initialize audit tracking
|
174
|
+
base.class_list :audit_log
|
175
|
+
base.class_hashkey :field_history
|
176
|
+
end
|
177
|
+
|
178
|
+
module ClassMethods
|
179
|
+
def enable_audit_for(*field_names)
|
180
|
+
@audited_fields ||= Set.new
|
181
|
+
@audited_fields.merge(field_names.map(&:to_sym))
|
182
|
+
end
|
183
|
+
|
184
|
+
def audited_fields
|
185
|
+
@audited_fields || Set.new
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
module InstanceMethods
|
190
|
+
def save
|
191
|
+
# Audit before saving
|
192
|
+
audit_changes if respond_to?(:audit_changes)
|
193
|
+
super
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def audit_changes
|
199
|
+
self.class.audited_fields.each do |field|
|
200
|
+
if instance_variable_changed?(field)
|
201
|
+
record_field_change(field)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def record_field_change(field)
|
207
|
+
change_record = {
|
208
|
+
field: field,
|
209
|
+
old_value: instance_variable_was(field),
|
210
|
+
new_value: instance_variable_get("@#{field}"),
|
211
|
+
timestamp: Time.now.to_f
|
212
|
+
}
|
213
|
+
|
214
|
+
self.class.audit_log.append(change_record.to_json)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Register with dependency on safe_dump
|
219
|
+
Familia::Base.add_feature self, :advanced_audit, depends_on: [:safe_dump]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Usage
|
225
|
+
class Customer < Familia::Horreum
|
226
|
+
feature :safe_dump # Dependency satisfied first
|
227
|
+
feature :advanced_audit # Now can be loaded
|
228
|
+
|
229
|
+
enable_audit_for :name, :email, :status
|
230
|
+
|
231
|
+
field :name, :email, :status, :created_at
|
232
|
+
end
|
233
|
+
```
|
234
|
+
|
235
|
+
### Feature with Custom Field Types
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
module Familia
|
239
|
+
module Features
|
240
|
+
module TimestampTracking
|
241
|
+
def self.included(base)
|
242
|
+
base.extend ClassMethods
|
243
|
+
|
244
|
+
# Add timestamp fields automatically
|
245
|
+
base.timestamp_field :created_at
|
246
|
+
base.timestamp_field :updated_at
|
247
|
+
end
|
248
|
+
|
249
|
+
module ClassMethods
|
250
|
+
def timestamp_field(name, auto_update: true)
|
251
|
+
# Create custom field type for timestamps
|
252
|
+
require_relative '../field_types/timestamp_field_type'
|
253
|
+
|
254
|
+
field_type = TimestampFieldType.new(
|
255
|
+
name,
|
256
|
+
auto_update: auto_update,
|
257
|
+
format: :iso8601
|
258
|
+
)
|
259
|
+
register_field_type(field_type)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Register feature
|
264
|
+
Familia::Base.add_feature self, :timestamp_tracking
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Custom field type (separate file)
|
270
|
+
class TimestampFieldType < Familia::FieldType
|
271
|
+
def initialize(name, auto_update: true, format: :unix, **options)
|
272
|
+
super(name, **options)
|
273
|
+
@auto_update = auto_update
|
274
|
+
@format = format
|
275
|
+
end
|
276
|
+
|
277
|
+
def serialize_value(record, value)
|
278
|
+
case @format
|
279
|
+
when :unix then value&.to_f
|
280
|
+
when :iso8601 then value&.iso8601
|
281
|
+
else value&.to_s
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def deserialize_value(record, stored_value)
|
286
|
+
return nil if stored_value.nil?
|
287
|
+
|
288
|
+
case @format
|
289
|
+
when :unix then Time.at(stored_value.to_f)
|
290
|
+
when :iso8601 then Time.parse(stored_value)
|
291
|
+
else Time.parse(stored_value)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
```
|
296
|
+
|
297
|
+
## Feature Dependencies
|
298
|
+
|
299
|
+
### Declaring Dependencies
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
# Feature with dependencies
|
303
|
+
Familia::Base.add_feature MyFeature, :my_feature, depends_on: [:safe_dump, :expiration]
|
304
|
+
|
305
|
+
# Will verify dependencies when feature is activated:
|
306
|
+
class Model < Familia::Horreum
|
307
|
+
feature :safe_dump # Must be loaded first
|
308
|
+
feature :expiration # Must be loaded first
|
309
|
+
feature :my_feature # Dependencies satisfied
|
310
|
+
end
|
311
|
+
```
|
312
|
+
|
313
|
+
### Dependency Validation
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
# This will raise an error:
|
317
|
+
class BadModel < Familia::Horreum
|
318
|
+
feature :my_feature # Error: requires safe_dump, expiration
|
319
|
+
end
|
320
|
+
# => Familia::Problem: my_feature requires: safe_dump, expiration
|
321
|
+
|
322
|
+
# Correct order:
|
323
|
+
class GoodModel < Familia::Horreum
|
324
|
+
feature :safe_dump
|
325
|
+
feature :expiration
|
326
|
+
feature :my_feature # ✅ Dependencies satisfied
|
327
|
+
end
|
328
|
+
```
|
329
|
+
|
330
|
+
## Method Conflict Resolution
|
331
|
+
|
332
|
+
### Conflict Detection
|
333
|
+
|
334
|
+
```ruby
|
335
|
+
class Customer < Familia::Horreum
|
336
|
+
field :status # Defines status= and status methods
|
337
|
+
|
338
|
+
# This would conflict with field-generated method
|
339
|
+
def status
|
340
|
+
"custom implementation" # ⚠️ Potential conflict
|
341
|
+
end
|
342
|
+
end
|
343
|
+
```
|
344
|
+
|
345
|
+
### Conflict Resolution Strategies
|
346
|
+
|
347
|
+
```ruby
|
348
|
+
# 1. Raise on conflict (default)
|
349
|
+
field :name, on_conflict: :raise # Raises if method exists
|
350
|
+
|
351
|
+
# 2. Skip definition if conflict
|
352
|
+
field :name, on_conflict: :skip # Skips if method exists
|
353
|
+
|
354
|
+
# 3. Warn but proceed
|
355
|
+
field :name, on_conflict: :warn # Warns but defines method
|
356
|
+
|
357
|
+
# 4. Ignore silently
|
358
|
+
field :name, on_conflict: :ignore # Proceeds without warning
|
359
|
+
```
|
360
|
+
|
361
|
+
### Using Prepend for Method Interception
|
362
|
+
|
363
|
+
```ruby
|
364
|
+
module MyFeature
|
365
|
+
def self.included(base)
|
366
|
+
# Use prepend to intercept method calls
|
367
|
+
base.prepend InstanceMethods
|
368
|
+
end
|
369
|
+
|
370
|
+
module InstanceMethods
|
371
|
+
def save
|
372
|
+
# Pre-processing
|
373
|
+
validate_before_save
|
374
|
+
|
375
|
+
# Call original save method
|
376
|
+
result = super
|
377
|
+
|
378
|
+
# Post-processing
|
379
|
+
notify_after_save
|
380
|
+
|
381
|
+
result
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
```
|
386
|
+
|
387
|
+
## Feature Categories and Field Types
|
388
|
+
|
389
|
+
### Field Categories
|
390
|
+
|
391
|
+
```ruby
|
392
|
+
class Document < Familia::Horreum
|
393
|
+
field :title # Regular field
|
394
|
+
field :content, category: :encrypted # Encrypted field
|
395
|
+
field :api_key, category: :transient # Transient field
|
396
|
+
field :tags, category: :indexed # Custom category
|
397
|
+
end
|
398
|
+
```
|
399
|
+
|
400
|
+
### Category-based Processing
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
# Features can process fields by category
|
404
|
+
module IndexingFeature
|
405
|
+
def self.included(base)
|
406
|
+
base.extend ClassMethods
|
407
|
+
|
408
|
+
# Process all :indexed category fields
|
409
|
+
base.field_definitions.select { |f| f.category == :indexed }.each do |field|
|
410
|
+
create_index_for(field.name)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
```
|
415
|
+
|
416
|
+
## Feature Discovery and Loading
|
417
|
+
|
418
|
+
### Automatic Loading
|
419
|
+
|
420
|
+
Features are automatically loaded from the `lib/familia/features/` directory:
|
421
|
+
|
422
|
+
```ruby
|
423
|
+
# lib/familia/features.rb automatically loads:
|
424
|
+
features_dir = File.join(__dir__, 'features')
|
425
|
+
Dir.glob(File.join(features_dir, '*.rb')).each do |feature_file|
|
426
|
+
require_relative feature_file
|
427
|
+
end
|
428
|
+
```
|
429
|
+
|
430
|
+
### Manual Feature Registration
|
431
|
+
|
432
|
+
```ruby
|
433
|
+
# For features outside the standard directory
|
434
|
+
class ExternalFeature
|
435
|
+
# Feature implementation...
|
436
|
+
end
|
437
|
+
|
438
|
+
# Register manually
|
439
|
+
Familia::Base.add_feature ExternalFeature, :external_feature, depends_on: []
|
440
|
+
```
|
441
|
+
|
442
|
+
## Advanced Usage Patterns
|
443
|
+
|
444
|
+
### Feature Composition
|
445
|
+
|
446
|
+
```ruby
|
447
|
+
class AdvancedModel < Familia::Horreum
|
448
|
+
# Combine multiple features for rich functionality
|
449
|
+
feature :expiration # TTL support
|
450
|
+
feature :safe_dump # API-safe serialization
|
451
|
+
feature :encrypted_fields # Secure storage
|
452
|
+
feature :quantization # Time-based bucketing
|
453
|
+
feature :transient_fields # Runtime secrets
|
454
|
+
|
455
|
+
# Now has capabilities from all features
|
456
|
+
field :name
|
457
|
+
encrypted_field :api_key
|
458
|
+
transient_field :session_token
|
459
|
+
quantized_field :metrics, interval: 1.hour
|
460
|
+
|
461
|
+
default_expiration 24.hours
|
462
|
+
safe_dump_fields :name, :created_at
|
463
|
+
end
|
464
|
+
```
|
465
|
+
|
466
|
+
### Conditional Feature Loading
|
467
|
+
|
468
|
+
```ruby
|
469
|
+
class ConfigurableModel < Familia::Horreum
|
470
|
+
# Load features based on configuration
|
471
|
+
if Rails.env.production?
|
472
|
+
feature :encrypted_fields
|
473
|
+
feature :advanced_audit
|
474
|
+
end
|
475
|
+
|
476
|
+
if defined?(Sidekiq)
|
477
|
+
feature :background_processing
|
478
|
+
end
|
479
|
+
|
480
|
+
feature :safe_dump # Always load
|
481
|
+
end
|
482
|
+
```
|
483
|
+
|
484
|
+
### Runtime Feature Checking
|
485
|
+
|
486
|
+
```ruby
|
487
|
+
class Model < Familia::Horreum
|
488
|
+
feature :expiration
|
489
|
+
|
490
|
+
def has_ttl_support?
|
491
|
+
self.class.features_enabled.include?(:expiration)
|
492
|
+
end
|
493
|
+
|
494
|
+
def available_features
|
495
|
+
self.class.features_enabled
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
model = Model.new
|
500
|
+
model.available_features # => [:expiration]
|
501
|
+
model.has_ttl_support? # => true
|
502
|
+
```
|
503
|
+
|
504
|
+
## Testing Features
|
505
|
+
|
506
|
+
### Feature Testing
|
507
|
+
|
508
|
+
```ruby
|
509
|
+
RSpec.describe MyCustomFeature do
|
510
|
+
let(:test_class) do
|
511
|
+
Class.new(Familia::Horreum) do
|
512
|
+
feature :my_custom_feature
|
513
|
+
field :name
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
it "includes feature methods" do
|
518
|
+
instance = test_class.new
|
519
|
+
expect(instance).to respond_to(:custom_instance_method)
|
520
|
+
expect(test_class).to respond_to(:custom_class_method)
|
521
|
+
end
|
522
|
+
|
523
|
+
it "validates dependencies" do
|
524
|
+
expect {
|
525
|
+
Class.new(Familia::Horreum) do
|
526
|
+
feature :advanced_audit # Missing safe_dump dependency
|
527
|
+
end
|
528
|
+
}.to raise_error(Familia::Problem, /requires.*safe_dump/)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
```
|
532
|
+
|
533
|
+
### Integration Testing
|
534
|
+
|
535
|
+
```ruby
|
536
|
+
RSpec.describe "Feature Integration" do
|
537
|
+
it "combines features correctly" do
|
538
|
+
combined_class = Class.new(Familia::Horreum) do
|
539
|
+
feature :safe_dump
|
540
|
+
feature :expiration
|
541
|
+
feature :encrypted_fields
|
542
|
+
|
543
|
+
field :name
|
544
|
+
encrypted_field :secret
|
545
|
+
safe_dump_fields :name
|
546
|
+
default_expiration 1.hour
|
547
|
+
end
|
548
|
+
|
549
|
+
instance = combined_class.new(name: "test", secret: "hidden")
|
550
|
+
|
551
|
+
# All features work together
|
552
|
+
expect(instance.safe_dump).to eq(name: "test")
|
553
|
+
expect(instance.secret).to eq("hidden") # Decrypted
|
554
|
+
expect(instance.ttl).to be > 0 # Has expiration
|
555
|
+
end
|
556
|
+
end
|
557
|
+
```
|
558
|
+
|
559
|
+
## Best Practices
|
560
|
+
|
561
|
+
### Feature Design
|
562
|
+
|
563
|
+
1. **Single Responsibility**: Each feature should have one clear purpose
|
564
|
+
2. **Minimal Dependencies**: Avoid complex dependency chains
|
565
|
+
3. **Graceful Degradation**: Handle missing dependencies gracefully
|
566
|
+
4. **Clear Naming**: Use descriptive feature and method names
|
567
|
+
5. **Documentation**: Document feature capabilities and usage
|
568
|
+
|
569
|
+
### Method Organization
|
570
|
+
|
571
|
+
```ruby
|
572
|
+
module MyFeature
|
573
|
+
def self.included(base)
|
574
|
+
base.extend ClassMethods
|
575
|
+
base.prepend InstanceMethods # For interception
|
576
|
+
base.include HelperMethods # For additional utilities
|
577
|
+
end
|
578
|
+
|
579
|
+
module ClassMethods
|
580
|
+
# Class-level functionality
|
581
|
+
end
|
582
|
+
|
583
|
+
module InstanceMethods
|
584
|
+
# Instance method interception/override
|
585
|
+
end
|
586
|
+
|
587
|
+
module HelperMethods
|
588
|
+
# Additional utility methods
|
589
|
+
end
|
590
|
+
end
|
591
|
+
```
|
592
|
+
|
593
|
+
### Performance Considerations
|
594
|
+
|
595
|
+
1. **Lazy Loading**: Initialize expensive resources only when needed
|
596
|
+
2. **Caching**: Cache computed values appropriately
|
597
|
+
3. **Method Interception**: Use prepend sparingly for performance-critical methods
|
598
|
+
4. **Field Processing**: Minimize overhead in field serialization/deserialization
|
599
|
+
|
600
|
+
The feature system provides a powerful foundation for extending Familia with reusable, composable functionality while maintaining clean separation of concerns and explicit dependency management.
|