familia 2.0.0.pre4 → 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/.gitignore +3 -0
- data/.rubocop_todo.yml +17 -17
- data/CLAUDE.md +11 -8
- data/Gemfile +5 -1
- data/Gemfile.lock +19 -3
- data/README.md +36 -157
- data/docs/overview.md +359 -0
- data/docs/wiki/API-Reference.md +347 -0
- data/docs/wiki/Connection-Pooling-Guide.md +437 -0
- data/docs/wiki/Encrypted-Fields-Overview.md +101 -0
- 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 +106 -0
- data/docs/wiki/Implementation-Guide.md +276 -0
- data/docs/wiki/Quantization-Feature-Guide.md +721 -0
- data/docs/wiki/RelatableObjects-Guide.md +563 -0
- data/docs/wiki/Security-Model.md +183 -0
- data/docs/wiki/Transient-Fields-Guide.md +280 -0
- data/lib/familia/base.rb +18 -27
- data/lib/familia/connection.rb +6 -5
- data/lib/familia/{datatype → data_type}/commands.rb +2 -5
- data/lib/familia/{datatype → data_type}/serialization.rb +8 -10
- data/lib/familia/data_type/types/counter.rb +38 -0
- data/lib/familia/{datatype → data_type}/types/hashkey.rb +20 -2
- data/lib/familia/{datatype → data_type}/types/list.rb +17 -18
- data/lib/familia/data_type/types/lock.rb +43 -0
- data/lib/familia/{datatype → data_type}/types/sorted_set.rb +17 -17
- data/lib/familia/{datatype → data_type}/types/string.rb +11 -3
- data/lib/familia/{datatype → data_type}/types/unsorted_set.rb +17 -18
- data/lib/familia/{datatype.rb → data_type.rb} +12 -14
- data/lib/familia/encryption/encrypted_data.rb +137 -0
- data/lib/familia/encryption/manager.rb +119 -0
- data/lib/familia/encryption/provider.rb +49 -0
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +123 -0
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +184 -0
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +138 -0
- data/lib/familia/encryption/registry.rb +50 -0
- data/lib/familia/encryption.rb +178 -0
- data/lib/familia/encryption_request_cache.rb +68 -0
- 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 +221 -0
- data/lib/familia/features/encrypted_fields.rb +28 -0
- data/lib/familia/features/expiration.rb +107 -77
- data/lib/familia/features/quantization.rb +5 -9
- data/lib/familia/features/relatable_objects.rb +2 -4
- data/lib/familia/features/safe_dump.rb +14 -17
- data/lib/familia/features/transient_fields/redacted_string.rb +159 -0
- data/lib/familia/features/transient_fields/single_use_redacted_string.rb +62 -0
- data/lib/familia/features/transient_fields/transient_field_type.rb +139 -0
- data/lib/familia/features/transient_fields.rb +47 -0
- data/lib/familia/features.rb +40 -24
- data/lib/familia/field_type.rb +273 -0
- data/lib/familia/horreum/{connection.rb → core/connection.rb} +6 -15
- data/lib/familia/horreum/{commands.rb → core/database_commands.rb} +20 -21
- data/lib/familia/horreum/core/serialization.rb +535 -0
- data/lib/familia/horreum/{utils.rb → core/utils.rb} +9 -12
- data/lib/familia/horreum/core.rb +21 -0
- data/lib/familia/horreum/{settings.rb → shared/settings.rb} +10 -4
- data/lib/familia/horreum/subclass/definition.rb +469 -0
- data/lib/familia/horreum/{class_methods.rb → subclass/management.rb} +27 -250
- data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
- data/lib/familia/horreum.rb +30 -22
- data/lib/familia/logging.rb +14 -14
- data/lib/familia/settings.rb +39 -3
- data/lib/familia/utils.rb +45 -0
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +3 -2
- data/try/core/base_enhancements_try.rb +115 -0
- data/try/core/connection_try.rb +0 -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 -5
- data/try/core/familia_extended_try.rb +3 -4
- data/try/core/familia_try.rb +1 -2
- data/try/core/persistence_operations_try.rb +297 -0
- data/try/core/pools_try.rb +2 -2
- data/try/core/secure_identifier_try.rb +0 -1
- data/try/core/settings_try.rb +0 -1
- data/try/core/utils_try.rb +0 -1
- data/try/{datatypes → data_types}/boolean_try.rb +1 -2
- data/try/data_types/counter_try.rb +93 -0
- data/try/{datatypes → data_types}/datatype_base_try.rb +2 -3
- data/try/{datatypes → data_types}/hash_try.rb +1 -2
- data/try/{datatypes → data_types}/list_try.rb +1 -2
- data/try/data_types/lock_try.rb +133 -0
- data/try/{datatypes → data_types}/set_try.rb +1 -2
- data/try/{datatypes → data_types}/sorted_set_try.rb +1 -2
- data/try/{datatypes → data_types}/string_try.rb +1 -2
- data/try/debugging/README.md +32 -0
- data/try/debugging/cache_behavior_tracer.rb +91 -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/debugging/encryption_method_tracer.rb +138 -0
- data/try/debugging/provider_diagnostics.rb +110 -0
- data/try/edge_cases/hash_symbolization_try.rb +0 -1
- data/try/edge_cases/json_serialization_try.rb +0 -1
- data/try/edge_cases/reserved_keywords_try.rb +42 -11
- data/try/encryption/config_persistence_try.rb +192 -0
- data/try/encryption/encryption_core_try.rb +328 -0
- data/try/encryption/instance_variable_scope_try.rb +31 -0
- data/try/encryption/module_loading_try.rb +28 -0
- data/try/encryption/providers/aes_gcm_provider_try.rb +178 -0
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +169 -0
- data/try/encryption/roundtrip_validation_try.rb +28 -0
- data/try/encryption/secure_memory_handling_try.rb +125 -0
- data/try/features/encrypted_fields_core_try.rb +125 -0
- data/try/features/encrypted_fields_integration_try.rb +216 -0
- data/try/features/encrypted_fields_no_cache_security_try.rb +219 -0
- data/try/features/encrypted_fields_security_try.rb +377 -0
- data/try/features/encryption_fields/aad_protection_try.rb +138 -0
- data/try/features/encryption_fields/concealed_string_core_try.rb +250 -0
- data/try/features/encryption_fields/context_isolation_try.rb +141 -0
- data/try/features/encryption_fields/error_conditions_try.rb +116 -0
- data/try/features/encryption_fields/fresh_key_derivation_try.rb +128 -0
- data/try/features/encryption_fields/fresh_key_try.rb +168 -0
- data/try/features/encryption_fields/key_rotation_try.rb +123 -0
- data/try/features/encryption_fields/memory_security_try.rb +37 -0
- data/try/features/encryption_fields/missing_current_key_version_try.rb +23 -0
- data/try/features/encryption_fields/nonce_uniqueness_try.rb +56 -0
- data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
- data/try/features/encryption_fields/thread_safety_try.rb +199 -0
- data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
- data/try/features/expiration_try.rb +0 -1
- data/try/features/feature_dependencies_try.rb +159 -0
- data/try/features/quantization_try.rb +0 -1
- data/try/features/real_feature_integration_try.rb +148 -0
- data/try/features/relatable_objects_try.rb +0 -1
- data/try/features/safe_dump_advanced_try.rb +0 -1
- data/try/features/safe_dump_try.rb +0 -1
- data/try/features/transient_fields/redacted_string_try.rb +248 -0
- data/try/features/transient_fields/refresh_reset_try.rb +164 -0
- data/try/features/transient_fields/simple_refresh_test.rb +50 -0
- data/try/features/transient_fields/single_use_redacted_string_try.rb +310 -0
- data/try/features/transient_fields_core_try.rb +181 -0
- data/try/features/transient_fields_integration_try.rb +260 -0
- data/try/helpers/test_helpers.rb +67 -0
- data/try/horreum/base_try.rb +157 -3
- data/try/horreum/enhanced_conflict_handling_try.rb +176 -0
- data/try/horreum/field_categories_try.rb +118 -0
- data/try/horreum/field_definition_try.rb +96 -0
- data/try/horreum/initialization_try.rb +1 -2
- data/try/horreum/relations_try.rb +1 -2
- data/try/horreum/serialization_persistent_fields_try.rb +165 -0
- data/try/horreum/serialization_try.rb +41 -7
- data/try/memory/memory_basic_test.rb +73 -0
- data/try/memory/memory_detailed_test.rb +121 -0
- data/try/memory/memory_docker_ruby_dump.sh +80 -0
- data/try/memory/memory_search_for_string.rb +83 -0
- data/try/memory/test_actual_redactedstring_protection.rb +38 -0
- data/try/models/customer_safe_dump_try.rb +1 -2
- data/try/models/customer_try.rb +1 -2
- data/try/models/datatype_base_try.rb +1 -2
- data/try/models/familia_object_try.rb +0 -1
- metadata +131 -23
- data/lib/familia/horreum/serialization.rb +0 -445
@@ -0,0 +1,892 @@
|
|
1
|
+
# Features System Developer Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
This developer guide covers the internal architecture of Familia's feature system, including feature registration, dependency resolution, loading mechanisms, and best practices for creating robust, maintainable features.
|
6
|
+
|
7
|
+
## Architecture Deep Dive
|
8
|
+
|
9
|
+
### Core Components
|
10
|
+
|
11
|
+
#### 1. Feature Registration (`Familia::Base`)
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# lib/familia/base.rb
|
15
|
+
module Familia::Base
|
16
|
+
@features_available = {} # Registry of available features
|
17
|
+
@feature_definitions = {} # Feature metadata and dependencies
|
18
|
+
|
19
|
+
def self.add_feature(klass, feature_name, depends_on: [])
|
20
|
+
@features_available ||= {}
|
21
|
+
|
22
|
+
# Create feature definition with metadata
|
23
|
+
feature_def = FeatureDefinition.new(
|
24
|
+
name: feature_name,
|
25
|
+
depends_on: depends_on,
|
26
|
+
)
|
27
|
+
|
28
|
+
@feature_definitions ||= {}
|
29
|
+
@feature_definitions[feature_name] = feature_def
|
30
|
+
features_available[feature_name] = klass
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
#### 2. Feature Activation (Horreum Classes)
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# When a class declares `feature :name`
|
39
|
+
module Familia::Horreum::ClassMethods
|
40
|
+
def feature(name)
|
41
|
+
# 1. Validate feature exists
|
42
|
+
feature_klass = Familia::Base.features_available[name]
|
43
|
+
raise Familia::Problem, "Unknown feature: #{name}" unless feature_klass
|
44
|
+
|
45
|
+
# 2. Check dependencies
|
46
|
+
validate_feature_dependencies(name)
|
47
|
+
|
48
|
+
# 3. Include the feature module
|
49
|
+
include feature_klass
|
50
|
+
|
51
|
+
# 4. Track enabled features
|
52
|
+
@features_enabled ||= Set.new
|
53
|
+
@features_enabled.add(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def validate_feature_dependencies(feature_name)
|
59
|
+
feature_def = Familia::Base.feature_definitions[feature_name]
|
60
|
+
return unless feature_def&.depends_on&.any?
|
61
|
+
|
62
|
+
missing_deps = feature_def.depends_on - features_enabled.to_a
|
63
|
+
if missing_deps.any?
|
64
|
+
raise Familia::Problem,
|
65
|
+
"Feature #{feature_name} requires: #{missing_deps.join(', ')}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
71
|
+
#### 3. Feature Definition Structure
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
class FeatureDefinition
|
75
|
+
attr_reader :name, :depends_on, :conflicts_with, :provides
|
76
|
+
|
77
|
+
def initialize(name:, depends_on: [], conflicts_with: [], provides: [])
|
78
|
+
@name = name.to_sym
|
79
|
+
@depends_on = Array(depends_on).map(&:to_sym)
|
80
|
+
@conflicts_with = Array(conflicts_with).map(&:to_sym)
|
81
|
+
@provides = Array(provides).map(&:to_sym)
|
82
|
+
end
|
83
|
+
|
84
|
+
def compatible_with?(other_feature)
|
85
|
+
!conflicts_with.include?(other_feature.name)
|
86
|
+
end
|
87
|
+
|
88
|
+
def dependencies_satisfied?(enabled_features)
|
89
|
+
depends_on.all? { |dep| enabled_features.include?(dep) }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
### Feature Loading Lifecycle
|
95
|
+
|
96
|
+
#### 1. Automatic Discovery
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
# lib/familia/features.rb - loads all features automatically
|
100
|
+
features_dir = File.join(__dir__, 'features')
|
101
|
+
Dir.glob(File.join(features_dir, '*.rb')).sort.each do |feature_file|
|
102
|
+
begin
|
103
|
+
require_relative feature_file
|
104
|
+
rescue LoadError => e
|
105
|
+
Familia.logger.warn "Failed to load feature #{feature_file}: #{e.message}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
#### 2. Feature Self-Registration
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
# Each feature registers itself when loaded
|
114
|
+
module Familia::Features::MyFeature
|
115
|
+
def self.included(base)
|
116
|
+
base.extend ClassMethods
|
117
|
+
base.prepend InstanceMethods
|
118
|
+
end
|
119
|
+
|
120
|
+
module ClassMethods
|
121
|
+
# Class-level functionality
|
122
|
+
end
|
123
|
+
|
124
|
+
module InstanceMethods
|
125
|
+
# Instance-level functionality
|
126
|
+
end
|
127
|
+
|
128
|
+
# Self-registration at module definition time
|
129
|
+
Familia::Base.add_feature self, :my_feature, depends_on: [:other_feature]
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
#### 3. Runtime Inclusion
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# When a class declares a feature
|
137
|
+
class MyModel < Familia::Horreum
|
138
|
+
feature :expiration # 1. Validation and dependency check
|
139
|
+
feature :encrypted_fields # 2. Module inclusion
|
140
|
+
feature :safe_dump # 3. Method definition and setup
|
141
|
+
end
|
142
|
+
```
|
143
|
+
|
144
|
+
## Advanced Feature Patterns
|
145
|
+
|
146
|
+
### Conditional Feature Loading
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
module Familia::Features::ConditionalFeature
|
150
|
+
def self.included(base)
|
151
|
+
# Only add functionality if conditions are met
|
152
|
+
if defined?(Rails) && Rails.env.production?
|
153
|
+
base.extend ProductionMethods
|
154
|
+
else
|
155
|
+
base.extend DevelopmentMethods
|
156
|
+
end
|
157
|
+
|
158
|
+
# Conditional method definitions based on available libraries
|
159
|
+
if defined?(Sidekiq)
|
160
|
+
base.include BackgroundJobIntegration
|
161
|
+
end
|
162
|
+
|
163
|
+
if defined?(ActiveRecord)
|
164
|
+
base.include ActiveRecordCompatibility
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
module ProductionMethods
|
169
|
+
def production_only_method
|
170
|
+
# Implementation only available in production
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
module DevelopmentMethods
|
175
|
+
def debug_helper_method
|
176
|
+
# Development and test helper methods
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Register with environment-specific dependencies
|
181
|
+
dependencies = []
|
182
|
+
dependencies << :logging if defined?(Rails)
|
183
|
+
dependencies << :metrics if ENV['ENABLE_METRICS']
|
184
|
+
|
185
|
+
Familia::Base.add_feature self, :conditional_feature, depends_on: dependencies
|
186
|
+
end
|
187
|
+
```
|
188
|
+
|
189
|
+
### Feature Conflicts and Compatibility
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
# Feature that conflicts with others
|
193
|
+
module Familia::Features::AlternativeImplementation
|
194
|
+
def self.included(base)
|
195
|
+
# Check for conflicting features
|
196
|
+
conflicting_features = [:original_implementation, :legacy_mode]
|
197
|
+
enabled_conflicts = conflicting_features & base.features_enabled.to_a
|
198
|
+
|
199
|
+
if enabled_conflicts.any?
|
200
|
+
raise Familia::Problem,
|
201
|
+
"#{self} conflicts with: #{enabled_conflicts.join(', ')}"
|
202
|
+
end
|
203
|
+
|
204
|
+
base.extend ClassMethods
|
205
|
+
end
|
206
|
+
|
207
|
+
module ClassMethods
|
208
|
+
def alternative_method
|
209
|
+
# Different implementation approach
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
Familia::Base.add_feature self, :alternative_implementation,
|
214
|
+
conflicts_with: [:original_implementation, :legacy_mode]
|
215
|
+
end
|
216
|
+
```
|
217
|
+
|
218
|
+
### Feature Capability Flags
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
module Familia::Features::CapabilityProvider
|
222
|
+
def self.included(base)
|
223
|
+
base.extend ClassMethods
|
224
|
+
|
225
|
+
# Add capability flags to the class
|
226
|
+
base.instance_variable_set(:@capabilities, Set.new)
|
227
|
+
base.capabilities.merge([:search, :indexing, :full_text])
|
228
|
+
end
|
229
|
+
|
230
|
+
module ClassMethods
|
231
|
+
attr_reader :capabilities
|
232
|
+
|
233
|
+
def has_capability?(capability)
|
234
|
+
capabilities.include?(capability.to_sym)
|
235
|
+
end
|
236
|
+
|
237
|
+
def requires_capability(capability)
|
238
|
+
unless has_capability?(capability)
|
239
|
+
raise Familia::Problem,
|
240
|
+
"#{self} requires #{capability} capability"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Feature provides capabilities that other features can depend on
|
246
|
+
Familia::Base.add_feature self, :capability_provider, provides: [:search, :indexing]
|
247
|
+
end
|
248
|
+
|
249
|
+
# Feature that requires specific capabilities
|
250
|
+
module Familia::Features::SearchDependent
|
251
|
+
def self.included(base)
|
252
|
+
# Check that required capabilities are available
|
253
|
+
base.requires_capability(:search)
|
254
|
+
base.requires_capability(:indexing)
|
255
|
+
|
256
|
+
base.extend ClassMethods
|
257
|
+
end
|
258
|
+
|
259
|
+
module ClassMethods
|
260
|
+
def search_by_field(field, query)
|
261
|
+
# Implementation that uses search capabilities
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
Familia::Base.add_feature self, :search_dependent,
|
266
|
+
depends_on: [:capability_provider]
|
267
|
+
end
|
268
|
+
```
|
269
|
+
|
270
|
+
### Dynamic Feature Configuration
|
271
|
+
|
272
|
+
```ruby
|
273
|
+
module Familia::Features::ConfigurableFeature
|
274
|
+
def self.included(base)
|
275
|
+
base.extend ClassMethods
|
276
|
+
|
277
|
+
# Initialize configuration
|
278
|
+
config = base.feature_config(:configurable_feature)
|
279
|
+
|
280
|
+
if config[:enable_caching]
|
281
|
+
base.include CachingMethods
|
282
|
+
end
|
283
|
+
|
284
|
+
if config[:enable_logging]
|
285
|
+
base.include LoggingMethods
|
286
|
+
end
|
287
|
+
|
288
|
+
# Configure behavior based on settings
|
289
|
+
base.instance_variable_set(:@batch_size, config[:batch_size] || 100)
|
290
|
+
end
|
291
|
+
|
292
|
+
module ClassMethods
|
293
|
+
def feature_config(feature_name)
|
294
|
+
@feature_configs ||= {}
|
295
|
+
@feature_configs[feature_name] ||= load_feature_config(feature_name)
|
296
|
+
end
|
297
|
+
|
298
|
+
private
|
299
|
+
|
300
|
+
def load_feature_config(feature_name)
|
301
|
+
# Load from various sources
|
302
|
+
config = {}
|
303
|
+
|
304
|
+
# 1. Default configuration
|
305
|
+
config.merge!(default_config_for(feature_name))
|
306
|
+
|
307
|
+
# 2. Environment variables
|
308
|
+
env_config = ENV.select { |k, v| k.start_with?("FAMILIA_#{feature_name.upcase}_") }
|
309
|
+
env_config.each { |k, v| config[k.split('_').last.downcase.to_sym] = v }
|
310
|
+
|
311
|
+
# 3. Configuration files
|
312
|
+
if defined?(Rails)
|
313
|
+
rails_config = Rails.application.config.familia&.features&.dig(feature_name)
|
314
|
+
config.merge!(rails_config) if rails_config
|
315
|
+
end
|
316
|
+
|
317
|
+
config
|
318
|
+
end
|
319
|
+
|
320
|
+
def default_config_for(feature_name)
|
321
|
+
case feature_name
|
322
|
+
when :configurable_feature
|
323
|
+
{
|
324
|
+
enable_caching: true,
|
325
|
+
enable_logging: Rails.env.development?,
|
326
|
+
batch_size: 100,
|
327
|
+
timeout: 30
|
328
|
+
}
|
329
|
+
else
|
330
|
+
{}
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
module CachingMethods
|
336
|
+
def cached_operation(&block)
|
337
|
+
# Caching implementation
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
module LoggingMethods
|
342
|
+
def log_operation(operation, &block)
|
343
|
+
# Logging implementation
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
Familia::Base.add_feature self, :configurable_feature
|
348
|
+
end
|
349
|
+
```
|
350
|
+
|
351
|
+
## Feature Development Best Practices
|
352
|
+
|
353
|
+
### 1. Feature Structure Template
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
# lib/familia/features/my_feature.rb
|
357
|
+
module Familia
|
358
|
+
module Features
|
359
|
+
module MyFeature
|
360
|
+
# Feature metadata
|
361
|
+
FEATURE_VERSION = '1.0.0'
|
362
|
+
REQUIRED_FAMILIA_VERSION = '>= 2.0.0'
|
363
|
+
|
364
|
+
def self.included(base)
|
365
|
+
# Validation and setup
|
366
|
+
validate_environment!(base)
|
367
|
+
|
368
|
+
Familia.ld "[#{base}] Loading #{self} v#{FEATURE_VERSION}"
|
369
|
+
|
370
|
+
# Module inclusion
|
371
|
+
base.extend ClassMethods
|
372
|
+
base.prepend InstanceMethods # Use prepend for method interception
|
373
|
+
base.include HelperMethods # Use include for utility methods
|
374
|
+
|
375
|
+
# Post-inclusion setup
|
376
|
+
configure_feature(base)
|
377
|
+
end
|
378
|
+
|
379
|
+
def self.validate_environment!(base)
|
380
|
+
# Check Familia version compatibility
|
381
|
+
familia_version = Gem::Version.new(Familia::VERSION)
|
382
|
+
required_version = Gem::Requirement.new(REQUIRED_FAMILIA_VERSION)
|
383
|
+
|
384
|
+
unless required_version.satisfied_by?(familia_version)
|
385
|
+
raise Familia::Problem,
|
386
|
+
"#{self} requires Familia #{REQUIRED_FAMILIA_VERSION}, " \
|
387
|
+
"got #{familia_version}"
|
388
|
+
end
|
389
|
+
|
390
|
+
# Check for required methods/capabilities on the base class
|
391
|
+
required_methods = [:identifier_field, :field]
|
392
|
+
missing_methods = required_methods.reject { |m| base.respond_to?(m) }
|
393
|
+
|
394
|
+
if missing_methods.any?
|
395
|
+
raise Familia::Problem,
|
396
|
+
"#{base} missing required methods: #{missing_methods.join(', ')}"
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def self.configure_feature(base)
|
401
|
+
# Feature-specific initialization
|
402
|
+
base.instance_variable_set(:@my_feature_config, {
|
403
|
+
enabled: true,
|
404
|
+
options: {}
|
405
|
+
})
|
406
|
+
|
407
|
+
# Set up feature-specific data structures
|
408
|
+
base.class_eval do
|
409
|
+
@my_feature_data ||= {}
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
# Class-level methods added to including class
|
414
|
+
module ClassMethods
|
415
|
+
def my_feature_config
|
416
|
+
@my_feature_config ||= { enabled: true, options: {} }
|
417
|
+
end
|
418
|
+
|
419
|
+
def configure_my_feature(**options)
|
420
|
+
my_feature_config[:options].merge!(options)
|
421
|
+
end
|
422
|
+
|
423
|
+
def my_feature_enabled?
|
424
|
+
my_feature_config[:enabled]
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
# Instance methods that intercept/override existing methods
|
429
|
+
module InstanceMethods
|
430
|
+
def save
|
431
|
+
# Pre-processing
|
432
|
+
before_my_feature_save if respond_to?(:before_my_feature_save, true)
|
433
|
+
|
434
|
+
# Call original save
|
435
|
+
result = super
|
436
|
+
|
437
|
+
# Post-processing
|
438
|
+
after_my_feature_save if respond_to?(:after_my_feature_save, true)
|
439
|
+
|
440
|
+
result
|
441
|
+
end
|
442
|
+
|
443
|
+
private
|
444
|
+
|
445
|
+
def before_my_feature_save
|
446
|
+
# Feature-specific pre-save logic
|
447
|
+
end
|
448
|
+
|
449
|
+
def after_my_feature_save
|
450
|
+
# Feature-specific post-save logic
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
# Utility methods that don't override existing functionality
|
455
|
+
module HelperMethods
|
456
|
+
def my_feature_helper
|
457
|
+
return unless self.class.my_feature_enabled?
|
458
|
+
# Helper implementation
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# Register the feature
|
463
|
+
Familia::Base.add_feature self, :my_feature, depends_on: [:required_feature]
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
```
|
468
|
+
|
469
|
+
### 2. Robust Error Handling
|
470
|
+
|
471
|
+
```ruby
|
472
|
+
module Familia::Features::RobustFeature
|
473
|
+
class FeatureError < Familia::Problem; end
|
474
|
+
class ConfigurationError < FeatureError; end
|
475
|
+
class DependencyError < FeatureError; end
|
476
|
+
|
477
|
+
def self.included(base)
|
478
|
+
begin
|
479
|
+
validate_dependencies!(base)
|
480
|
+
configure_feature_safely(base)
|
481
|
+
rescue => e
|
482
|
+
handle_inclusion_error(base, e)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def self.validate_dependencies!(base)
|
487
|
+
# Check external dependencies
|
488
|
+
unless defined?(SomeGem)
|
489
|
+
raise DependencyError, "#{self} requires 'some_gem' gem"
|
490
|
+
end
|
491
|
+
|
492
|
+
# Check feature dependencies
|
493
|
+
required_features = [:base_feature]
|
494
|
+
missing_features = required_features - base.features_enabled.to_a
|
495
|
+
|
496
|
+
if missing_features.any?
|
497
|
+
raise DependencyError,
|
498
|
+
"#{self} requires features: #{missing_features.join(', ')}"
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
def self.configure_feature_safely(base)
|
503
|
+
# Safely configure with fallbacks
|
504
|
+
config = load_configuration
|
505
|
+
apply_configuration(base, config)
|
506
|
+
rescue => e
|
507
|
+
Familia.logger.warn "Feature configuration failed: #{e.message}"
|
508
|
+
apply_default_configuration(base)
|
509
|
+
end
|
510
|
+
|
511
|
+
def self.handle_inclusion_error(base, error)
|
512
|
+
case error
|
513
|
+
when DependencyError
|
514
|
+
# Log dependency issues and disable feature
|
515
|
+
Familia.logger.error "Feature #{self} disabled: #{error.message}"
|
516
|
+
base.instance_variable_set(:@robust_feature_disabled, true)
|
517
|
+
when ConfigurationError
|
518
|
+
# Try default configuration
|
519
|
+
Familia.logger.warn "Using default configuration: #{error.message}"
|
520
|
+
apply_default_configuration(base)
|
521
|
+
else
|
522
|
+
# Re-raise unexpected errors
|
523
|
+
raise
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
module ClassMethods
|
528
|
+
def robust_feature_enabled?
|
529
|
+
!@robust_feature_disabled
|
530
|
+
end
|
531
|
+
|
532
|
+
def with_robust_feature(&block)
|
533
|
+
return unless robust_feature_enabled?
|
534
|
+
block.call
|
535
|
+
rescue => e
|
536
|
+
Familia.logger.error "Robust feature operation failed: #{e.message}"
|
537
|
+
nil
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
Familia::Base.add_feature self, :robust_feature
|
542
|
+
end
|
543
|
+
```
|
544
|
+
|
545
|
+
### 3. Feature Testing Infrastructure
|
546
|
+
|
547
|
+
```ruby
|
548
|
+
# Test helpers for feature development
|
549
|
+
module FeatureTestHelpers
|
550
|
+
def with_feature(feature_name, config = {})
|
551
|
+
# Create temporary test class with feature
|
552
|
+
test_class = Class.new(Familia::Horreum) do
|
553
|
+
def self.name
|
554
|
+
'FeatureTestClass'
|
555
|
+
end
|
556
|
+
|
557
|
+
identifier_field :test_id
|
558
|
+
field :test_id
|
559
|
+
end
|
560
|
+
|
561
|
+
# Configure feature if needed
|
562
|
+
if config.any?
|
563
|
+
test_class.define_singleton_method(:feature_config) do |name|
|
564
|
+
config
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
# Enable the feature
|
569
|
+
test_class.feature feature_name
|
570
|
+
|
571
|
+
yield test_class
|
572
|
+
end
|
573
|
+
|
574
|
+
def feature_enabled?(klass, feature_name)
|
575
|
+
klass.features_enabled.include?(feature_name)
|
576
|
+
end
|
577
|
+
|
578
|
+
def assert_feature_methods(klass, expected_methods)
|
579
|
+
expected_methods.each do |method|
|
580
|
+
assert klass.method_defined?(method),
|
581
|
+
"Expected #{klass} to have method #{method}"
|
582
|
+
end
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
# RSpec helper
|
587
|
+
RSpec.configure do |config|
|
588
|
+
config.include FeatureTestHelpers
|
589
|
+
end
|
590
|
+
|
591
|
+
# Feature test example
|
592
|
+
RSpec.describe Familia::Features::MyFeature do
|
593
|
+
it "adds expected methods to class" do
|
594
|
+
with_feature(:my_feature) do |test_class|
|
595
|
+
expect(test_class).to respond_to(:my_feature_config)
|
596
|
+
expect(test_class.new).to respond_to(:my_feature_helper)
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
it "respects feature configuration" do
|
601
|
+
config = { enabled: false }
|
602
|
+
|
603
|
+
with_feature(:my_feature, config) do |test_class|
|
604
|
+
expect(test_class.my_feature_enabled?).to be false
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
it "validates dependencies" do
|
609
|
+
expect {
|
610
|
+
Class.new(Familia::Horreum) do
|
611
|
+
feature :my_feature # Missing :required_feature dependency
|
612
|
+
end
|
613
|
+
}.to raise_error(Familia::Problem, /requires.*required_feature/)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
```
|
617
|
+
|
618
|
+
## Performance Optimization
|
619
|
+
|
620
|
+
### 1. Lazy Feature Loading
|
621
|
+
|
622
|
+
```ruby
|
623
|
+
module Familia::Features::LazyFeature
|
624
|
+
def self.included(base)
|
625
|
+
# Minimal setup at include time
|
626
|
+
base.extend ClassMethods
|
627
|
+
|
628
|
+
# Defer expensive setup until first use
|
629
|
+
@setup_complete = false
|
630
|
+
end
|
631
|
+
|
632
|
+
module ClassMethods
|
633
|
+
def ensure_lazy_feature_setup!
|
634
|
+
return if @setup_complete
|
635
|
+
|
636
|
+
# Expensive setup operations
|
637
|
+
perform_expensive_setup
|
638
|
+
@setup_complete = true
|
639
|
+
end
|
640
|
+
|
641
|
+
def lazy_feature_method
|
642
|
+
ensure_lazy_feature_setup!
|
643
|
+
# Method implementation
|
644
|
+
end
|
645
|
+
|
646
|
+
private
|
647
|
+
|
648
|
+
def perform_expensive_setup
|
649
|
+
# Heavy initialization work
|
650
|
+
@expensive_data = load_expensive_data
|
651
|
+
@compiled_templates = compile_templates
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
Familia::Base.add_feature self, :lazy_feature
|
656
|
+
end
|
657
|
+
```
|
658
|
+
|
659
|
+
### 2. Feature Method Caching
|
660
|
+
|
661
|
+
```ruby
|
662
|
+
module Familia::Features::CachedFeature
|
663
|
+
def self.included(base)
|
664
|
+
base.extend ClassMethods
|
665
|
+
end
|
666
|
+
|
667
|
+
module ClassMethods
|
668
|
+
def cached_feature_method(key)
|
669
|
+
@method_cache ||= {}
|
670
|
+
@method_cache[key] ||= expensive_computation(key)
|
671
|
+
end
|
672
|
+
|
673
|
+
def clear_feature_cache!
|
674
|
+
@method_cache = {}
|
675
|
+
end
|
676
|
+
|
677
|
+
private
|
678
|
+
|
679
|
+
def expensive_computation(key)
|
680
|
+
# Expensive operation
|
681
|
+
sleep 0.1 # Simulate work
|
682
|
+
"computed_#{key}"
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
Familia::Base.add_feature self, :cached_feature
|
687
|
+
end
|
688
|
+
```
|
689
|
+
|
690
|
+
## Debugging Features
|
691
|
+
|
692
|
+
### 1. Feature Introspection
|
693
|
+
|
694
|
+
```ruby
|
695
|
+
module Familia::Features::Introspection
|
696
|
+
def self.included(base)
|
697
|
+
base.extend ClassMethods
|
698
|
+
end
|
699
|
+
|
700
|
+
module ClassMethods
|
701
|
+
def feature_info
|
702
|
+
{
|
703
|
+
enabled_features: features_enabled.to_a,
|
704
|
+
feature_dependencies: feature_dependency_graph,
|
705
|
+
feature_conflicts: feature_conflict_map,
|
706
|
+
feature_load_order: feature_load_order
|
707
|
+
}
|
708
|
+
end
|
709
|
+
|
710
|
+
def feature_dependency_graph
|
711
|
+
graph = {}
|
712
|
+
features_enabled.each do |feature|
|
713
|
+
definition = Familia::Base.feature_definitions[feature]
|
714
|
+
graph[feature] = definition&.depends_on || []
|
715
|
+
end
|
716
|
+
graph
|
717
|
+
end
|
718
|
+
|
719
|
+
def feature_conflict_map
|
720
|
+
conflicts = {}
|
721
|
+
features_enabled.each do |feature|
|
722
|
+
definition = Familia::Base.feature_definitions[feature]
|
723
|
+
conflicts[feature] = definition&.conflicts_with || []
|
724
|
+
end
|
725
|
+
conflicts
|
726
|
+
end
|
727
|
+
|
728
|
+
def feature_load_order
|
729
|
+
# Return the order features were loaded
|
730
|
+
@feature_load_order ||= []
|
731
|
+
end
|
732
|
+
|
733
|
+
def debug_feature_issues
|
734
|
+
issues = []
|
735
|
+
|
736
|
+
# Check for circular dependencies
|
737
|
+
issues.concat(detect_circular_dependencies)
|
738
|
+
|
739
|
+
# Check for method conflicts
|
740
|
+
issues.concat(detect_method_conflicts)
|
741
|
+
|
742
|
+
# Check for missing dependencies
|
743
|
+
issues.concat(detect_missing_dependencies)
|
744
|
+
|
745
|
+
issues
|
746
|
+
end
|
747
|
+
|
748
|
+
private
|
749
|
+
|
750
|
+
def detect_circular_dependencies
|
751
|
+
# Implementation for circular dependency detection
|
752
|
+
end
|
753
|
+
|
754
|
+
def detect_method_conflicts
|
755
|
+
# Implementation for method conflict detection
|
756
|
+
end
|
757
|
+
|
758
|
+
def detect_missing_dependencies
|
759
|
+
# Implementation for missing dependency detection
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
Familia::Base.add_feature self, :introspection
|
764
|
+
end
|
765
|
+
```
|
766
|
+
|
767
|
+
### 2. Feature Debug Logging
|
768
|
+
|
769
|
+
```ruby
|
770
|
+
module Familia::Features::DebugLogging
|
771
|
+
def self.included(base)
|
772
|
+
return unless Familia.debug?
|
773
|
+
|
774
|
+
base.extend ClassMethods
|
775
|
+
original_feature_method = base.method(:feature)
|
776
|
+
|
777
|
+
base.define_singleton_method(:feature) do |name|
|
778
|
+
Familia.ld "[DEBUG] Loading feature #{name} on #{self}"
|
779
|
+
start_time = Time.now
|
780
|
+
|
781
|
+
result = original_feature_method.call(name)
|
782
|
+
|
783
|
+
load_time = (Time.now - start_time) * 1000
|
784
|
+
Familia.ld "[DEBUG] Feature #{name} loaded in #{load_time.round(2)}ms"
|
785
|
+
|
786
|
+
result
|
787
|
+
end
|
788
|
+
end
|
789
|
+
|
790
|
+
module ClassMethods
|
791
|
+
def log_feature_method_call(method_name, &block)
|
792
|
+
return block.call unless Familia.debug?
|
793
|
+
|
794
|
+
Familia.ld "[DEBUG] Calling #{method_name} on #{self}"
|
795
|
+
start_time = Time.now
|
796
|
+
|
797
|
+
result = block.call
|
798
|
+
|
799
|
+
duration = (Time.now - start_time) * 1000
|
800
|
+
Familia.ld "[DEBUG] #{method_name} completed in #{duration.round(2)}ms"
|
801
|
+
|
802
|
+
result
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
806
|
+
Familia::Base.add_feature self, :debug_logging
|
807
|
+
end
|
808
|
+
```
|
809
|
+
|
810
|
+
## Migration and Versioning
|
811
|
+
|
812
|
+
### Feature Versioning
|
813
|
+
|
814
|
+
```ruby
|
815
|
+
module Familia::Features::VersionedFeature
|
816
|
+
VERSION = '2.1.0'
|
817
|
+
MIGRATION_PATH = [
|
818
|
+
{ from: '1.0.0', to: '1.1.0', migration: :migrate_1_0_to_1_1 },
|
819
|
+
{ from: '1.1.0', to: '2.0.0', migration: :migrate_1_1_to_2_0 },
|
820
|
+
{ from: '2.0.0', to: '2.1.0', migration: :migrate_2_0_to_2_1 }
|
821
|
+
].freeze
|
822
|
+
|
823
|
+
def self.included(base)
|
824
|
+
check_and_migrate_version(base)
|
825
|
+
base.extend ClassMethods
|
826
|
+
end
|
827
|
+
|
828
|
+
def self.check_and_migrate_version(base)
|
829
|
+
current_version = get_current_version(base)
|
830
|
+
return if current_version == VERSION
|
831
|
+
|
832
|
+
if current_version.nil?
|
833
|
+
# First installation
|
834
|
+
set_version(base, VERSION)
|
835
|
+
return
|
836
|
+
end
|
837
|
+
|
838
|
+
# Perform migration
|
839
|
+
migrate_from_version(base, current_version, VERSION)
|
840
|
+
end
|
841
|
+
|
842
|
+
def self.migrate_from_version(base, from_version, to_version)
|
843
|
+
migration_steps = find_migration_path(from_version, to_version)
|
844
|
+
|
845
|
+
migration_steps.each do |step|
|
846
|
+
Familia.logger.info "Migrating #{base} from #{step[:from]} to #{step[:to]}"
|
847
|
+
send(step[:migration], base)
|
848
|
+
end
|
849
|
+
|
850
|
+
set_version(base, to_version)
|
851
|
+
end
|
852
|
+
|
853
|
+
def self.find_migration_path(from, to)
|
854
|
+
# Find path through migration steps
|
855
|
+
current = from
|
856
|
+
path = []
|
857
|
+
|
858
|
+
while current != to
|
859
|
+
step = MIGRATION_PATH.find { |s| s[:from] == current }
|
860
|
+
break unless step
|
861
|
+
|
862
|
+
path << step
|
863
|
+
current = step[:to]
|
864
|
+
end
|
865
|
+
|
866
|
+
path
|
867
|
+
end
|
868
|
+
|
869
|
+
# Migration methods
|
870
|
+
def self.migrate_1_0_to_1_1(base)
|
871
|
+
# Migration logic for 1.0 -> 1.1
|
872
|
+
end
|
873
|
+
|
874
|
+
def self.migrate_1_1_to_2_0(base)
|
875
|
+
# Migration logic for 1.1 -> 2.0
|
876
|
+
end
|
877
|
+
|
878
|
+
def self.migrate_2_0_to_2_1(base)
|
879
|
+
# Migration logic for 2.0 -> 2.1
|
880
|
+
end
|
881
|
+
|
882
|
+
module ClassMethods
|
883
|
+
def feature_version
|
884
|
+
self.class.instance_variable_get(:@versioned_feature_version) || VERSION
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
Familia::Base.add_feature self, :versioned_feature
|
889
|
+
end
|
890
|
+
```
|
891
|
+
|
892
|
+
This developer guide provides the foundation for creating robust, maintainable features that integrate seamlessly with Familia's architecture while following best practices for error handling, performance, and maintainability.
|