familia 2.0.0.pre5 → 2.0.0.pre7

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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +57 -0
  3. data/.github/workflows/claude.yml +71 -0
  4. data/.gitignore +5 -1
  5. data/.rubocop.yml +3 -0
  6. data/CLAUDE.md +32 -10
  7. data/Gemfile +2 -2
  8. data/Gemfile.lock +4 -3
  9. data/docs/wiki/API-Reference.md +95 -18
  10. data/docs/wiki/Connection-Pooling-Guide.md +437 -0
  11. data/docs/wiki/Encrypted-Fields-Overview.md +40 -3
  12. data/docs/wiki/Expiration-Feature-Guide.md +596 -0
  13. data/docs/wiki/Feature-System-Guide.md +631 -0
  14. data/docs/wiki/Features-System-Developer-Guide.md +892 -0
  15. data/docs/wiki/Field-System-Guide.md +784 -0
  16. data/docs/wiki/Home.md +82 -15
  17. data/docs/wiki/Implementation-Guide.md +126 -33
  18. data/docs/wiki/Quantization-Feature-Guide.md +721 -0
  19. data/docs/wiki/Relationships-Guide.md +684 -0
  20. data/docs/wiki/Security-Model.md +65 -25
  21. data/docs/wiki/Transient-Fields-Guide.md +280 -0
  22. data/examples/bit_encoding_integration.rb +237 -0
  23. data/examples/redis_command_validation_example.rb +231 -0
  24. data/examples/relationships_basic.rb +273 -0
  25. data/lib/familia/base.rb +1 -1
  26. data/lib/familia/connection.rb +3 -3
  27. data/lib/familia/data_type/types/counter.rb +38 -0
  28. data/lib/familia/data_type/types/hashkey.rb +18 -0
  29. data/lib/familia/data_type/types/lock.rb +43 -0
  30. data/lib/familia/data_type/types/string.rb +9 -2
  31. data/lib/familia/data_type.rb +9 -6
  32. data/lib/familia/encryption/encrypted_data.rb +137 -0
  33. data/lib/familia/encryption/manager.rb +21 -4
  34. data/lib/familia/encryption/providers/aes_gcm_provider.rb +20 -0
  35. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +20 -0
  36. data/lib/familia/encryption.rb +1 -1
  37. data/lib/familia/errors.rb +17 -3
  38. data/lib/familia/features/encrypted_fields/concealed_string.rb +293 -0
  39. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +94 -26
  40. data/lib/familia/features/encrypted_fields.rb +413 -4
  41. data/lib/familia/features/expiration.rb +319 -33
  42. data/lib/familia/features/quantization.rb +385 -44
  43. data/lib/familia/features/relationships/cascading.rb +438 -0
  44. data/lib/familia/features/relationships/indexing.rb +370 -0
  45. data/lib/familia/features/relationships/membership.rb +503 -0
  46. data/lib/familia/features/relationships/permission_management.rb +264 -0
  47. data/lib/familia/features/relationships/querying.rb +620 -0
  48. data/lib/familia/features/relationships/redis_operations.rb +274 -0
  49. data/lib/familia/features/relationships/score_encoding.rb +442 -0
  50. data/lib/familia/features/relationships/tracking.rb +379 -0
  51. data/lib/familia/features/relationships.rb +466 -0
  52. data/lib/familia/features/safe_dump.rb +1 -1
  53. data/lib/familia/features/transient_fields/redacted_string.rb +1 -1
  54. data/lib/familia/features/transient_fields.rb +192 -10
  55. data/lib/familia/features.rb +2 -1
  56. data/lib/familia/field_type.rb +5 -2
  57. data/lib/familia/horreum/{connection.rb → core/connection.rb} +2 -8
  58. data/lib/familia/horreum/{database_commands.rb → core/database_commands.rb} +14 -3
  59. data/lib/familia/horreum/core/serialization.rb +535 -0
  60. data/lib/familia/horreum/{utils.rb → core/utils.rb} +0 -2
  61. data/lib/familia/horreum/core.rb +21 -0
  62. data/lib/familia/horreum/{settings.rb → shared/settings.rb} +0 -2
  63. data/lib/familia/horreum/{definition_methods.rb → subclass/definition.rb} +45 -29
  64. data/lib/familia/horreum/{management_methods.rb → subclass/management.rb} +9 -8
  65. data/lib/familia/horreum/{related_fields_management.rb → subclass/related_fields_management.rb} +15 -10
  66. data/lib/familia/horreum.rb +17 -17
  67. data/lib/familia/validation/command_recorder.rb +336 -0
  68. data/lib/familia/validation/expectations.rb +519 -0
  69. data/lib/familia/validation/test_helpers.rb +443 -0
  70. data/lib/familia/validation/validator.rb +412 -0
  71. data/lib/familia/validation.rb +140 -0
  72. data/lib/familia/version.rb +1 -1
  73. data/lib/familia.rb +1 -1
  74. data/try/core/create_method_try.rb +240 -0
  75. data/try/core/database_consistency_try.rb +299 -0
  76. data/try/core/errors_try.rb +25 -4
  77. data/try/core/familia_try.rb +1 -1
  78. data/try/core/persistence_operations_try.rb +297 -0
  79. data/try/data_types/counter_try.rb +93 -0
  80. data/try/data_types/lock_try.rb +133 -0
  81. data/try/debugging/debug_aad_process.rb +82 -0
  82. data/try/debugging/debug_concealed_internal.rb +59 -0
  83. data/try/debugging/debug_concealed_reveal.rb +61 -0
  84. data/try/debugging/debug_context_aad.rb +68 -0
  85. data/try/debugging/debug_context_simple.rb +80 -0
  86. data/try/debugging/debug_cross_context.rb +62 -0
  87. data/try/debugging/debug_database_load.rb +64 -0
  88. data/try/debugging/debug_encrypted_json_check.rb +53 -0
  89. data/try/debugging/debug_encrypted_json_step_by_step.rb +62 -0
  90. data/try/debugging/debug_exists_lifecycle.rb +54 -0
  91. data/try/debugging/debug_field_decrypt.rb +74 -0
  92. data/try/debugging/debug_fresh_cross_context.rb +73 -0
  93. data/try/debugging/debug_load_path.rb +66 -0
  94. data/try/debugging/debug_method_definition.rb +46 -0
  95. data/try/debugging/debug_method_resolution.rb +41 -0
  96. data/try/debugging/debug_minimal.rb +24 -0
  97. data/try/debugging/debug_provider.rb +68 -0
  98. data/try/debugging/debug_secure_behavior.rb +73 -0
  99. data/try/debugging/debug_string_class.rb +46 -0
  100. data/try/debugging/debug_test.rb +46 -0
  101. data/try/debugging/debug_test_design.rb +80 -0
  102. data/try/edge_cases/hash_symbolization_try.rb +1 -0
  103. data/try/edge_cases/reserved_keywords_try.rb +1 -0
  104. data/try/edge_cases/string_coercion_try.rb +2 -0
  105. data/try/encryption/encryption_core_try.rb +6 -4
  106. data/try/features/categorical_permissions_try.rb +515 -0
  107. data/try/features/encrypted_fields_core_try.rb +19 -11
  108. data/try/features/encrypted_fields_integration_try.rb +66 -70
  109. data/try/features/encrypted_fields_no_cache_security_try.rb +22 -8
  110. data/try/features/encrypted_fields_security_try.rb +151 -144
  111. data/try/features/encryption_fields/aad_protection_try.rb +108 -23
  112. data/try/features/encryption_fields/concealed_string_core_try.rb +253 -0
  113. data/try/features/encryption_fields/context_isolation_try.rb +30 -8
  114. data/try/features/encryption_fields/error_conditions_try.rb +6 -6
  115. data/try/features/encryption_fields/fresh_key_derivation_try.rb +20 -14
  116. data/try/features/encryption_fields/fresh_key_try.rb +27 -22
  117. data/try/features/encryption_fields/key_rotation_try.rb +16 -10
  118. data/try/features/encryption_fields/nonce_uniqueness_try.rb +15 -13
  119. data/try/features/encryption_fields/secure_by_default_behavior_try.rb +310 -0
  120. data/try/features/encryption_fields/thread_safety_try.rb +6 -6
  121. data/try/features/encryption_fields/universal_serialization_safety_try.rb +174 -0
  122. data/try/features/feature_dependencies_try.rb +3 -3
  123. data/try/features/relationships_edge_cases_try.rb +145 -0
  124. data/try/features/relationships_performance_minimal_try.rb +132 -0
  125. data/try/features/relationships_performance_simple_try.rb +155 -0
  126. data/try/features/relationships_performance_try.rb +420 -0
  127. data/try/features/relationships_performance_working_try.rb +144 -0
  128. data/try/features/relationships_try.rb +237 -0
  129. data/try/features/safe_dump_try.rb +3 -0
  130. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  131. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  132. data/try/features/transient_fields_core_try.rb +1 -1
  133. data/try/features/transient_fields_integration_try.rb +1 -1
  134. data/try/helpers/test_helpers.rb +26 -1
  135. data/try/horreum/base_try.rb +14 -8
  136. data/try/horreum/enhanced_conflict_handling_try.rb +3 -1
  137. data/try/horreum/initialization_try.rb +1 -1
  138. data/try/horreum/relations_try.rb +2 -2
  139. data/try/horreum/serialization_persistent_fields_try.rb +8 -8
  140. data/try/horreum/serialization_try.rb +39 -4
  141. data/try/models/customer_safe_dump_try.rb +1 -1
  142. data/try/models/customer_try.rb +1 -1
  143. data/try/validation/atomic_operations_try.rb.disabled +320 -0
  144. data/try/validation/command_validation_try.rb.disabled +207 -0
  145. data/try/validation/performance_validation_try.rb.disabled +324 -0
  146. data/try/validation/real_world_scenarios_try.rb.disabled +390 -0
  147. metadata +81 -12
  148. data/TEST_COVERAGE.md +0 -40
  149. data/lib/familia/features/relatable_objects.rb +0 -125
  150. data/lib/familia/horreum/serialization.rb +0 -473
  151. data/try/features/relatable_objects_try.rb +0 -220
@@ -0,0 +1,784 @@
1
+ # Field System Guide
2
+
3
+ ## Overview
4
+
5
+ Familia's Field System provides a flexible, extensible architecture for defining and managing object attributes with customizable behavior, conflict resolution, and serialization. The system uses a FieldType-based architecture that separates field definition from implementation, enabling custom field behaviors and advanced features.
6
+
7
+ ## Core Architecture
8
+
9
+ ### FieldType System
10
+
11
+ The Field System is built around the `FieldType` class hierarchy:
12
+
13
+ ```ruby
14
+ FieldType # Base class for all field types
15
+ ├── TransientFieldType # Non-persistent fields (memory only)
16
+ ├── EncryptedFieldType # Encrypted storage fields
17
+ └── Custom field types # User-defined field behaviors
18
+ ```
19
+
20
+ ### Field Definition Flow
21
+
22
+ 1. **Field Declaration**: `field :name, options...`
23
+ 2. **FieldType Creation**: Appropriate FieldType instance created
24
+ 3. **Registration**: FieldType registered with the class
25
+ 4. **Method Installation**: Getter, setter, and fast methods defined
26
+ 5. **Runtime Usage**: Methods available on instances
27
+
28
+ ## Basic Usage
29
+
30
+ ### Simple Field Definition
31
+
32
+ ```ruby
33
+ class Customer < Familia::Horreum
34
+ # Basic field with default settings
35
+ field :name
36
+
37
+ # Field with custom method name
38
+ field :email_address, as: :email
39
+
40
+ # Field without accessor methods
41
+ field :internal_data, as: false
42
+
43
+ # Field without fast writer method
44
+ field :readonly_data, fast_method: false
45
+ end
46
+
47
+ customer = Customer.new
48
+ customer.name = "Acme Corp" # Standard setter
49
+ customer.email = "admin@acme.com" # Custom method name
50
+ customer.name!("Updated Corp") # Fast writer (immediate DB persistence)
51
+ ```
52
+
53
+ ### Field Categories
54
+
55
+ Fields can be categorized for special processing by features:
56
+
57
+ ```ruby
58
+ class Document < Familia::Horreum
59
+ field :title # Regular field
60
+ field :content, category: :encrypted # Will be processed by encrypted_fields feature
61
+ field :api_key, category: :transient # Non-persistent field
62
+ field :tags, category: :indexed # Custom category for indexing
63
+ field :metadata, category: :json # Custom JSON serialization
64
+ end
65
+ ```
66
+
67
+ ### Method Conflict Resolution
68
+
69
+ The Field System provides multiple strategies for handling method name conflicts:
70
+
71
+ ```ruby
72
+ class Customer < Familia::Horreum
73
+ # Raise error if method exists (default)
74
+ field :status, on_conflict: :raise
75
+
76
+ # Skip field definition if method exists
77
+ field :type, on_conflict: :skip
78
+
79
+ # Warn but proceed with definition
80
+ field :class, on_conflict: :warn
81
+
82
+ # Silently overwrite existing method
83
+ field :id, on_conflict: :overwrite
84
+ end
85
+ ```
86
+
87
+ ## Advanced Field Types
88
+
89
+ ### Creating Custom Field Types
90
+
91
+ ```ruby
92
+ # Custom field type for timestamps
93
+ class TimestampFieldType < Familia::FieldType
94
+ def category
95
+ :timestamp
96
+ end
97
+
98
+ def define_setter(klass)
99
+ field_name = @name
100
+ method_name = @method_name
101
+
102
+ handle_method_conflict(klass, :"#{method_name}=") do
103
+ klass.define_method :"#{method_name}=" do |value|
104
+ # Convert various formats to Unix timestamp
105
+ timestamp = case value
106
+ when Time then value.to_i
107
+ when String then Time.parse(value).to_i
108
+ when Numeric then value.to_i
109
+ else raise ArgumentError, "Invalid timestamp: #{value}"
110
+ end
111
+ instance_variable_set(:"@#{field_name}", timestamp)
112
+ end
113
+ end
114
+ end
115
+
116
+ def define_getter(klass)
117
+ field_name = @name
118
+ method_name = @method_name
119
+
120
+ handle_method_conflict(klass, method_name) do
121
+ klass.define_method method_name do
122
+ timestamp = instance_variable_get(:"@#{field_name}")
123
+ timestamp ? Time.at(timestamp) : nil
124
+ end
125
+ end
126
+ end
127
+
128
+ def serialize(value, _record = nil)
129
+ value.respond_to?(:to_i) ? value.to_i : value
130
+ end
131
+
132
+ def deserialize(value, _record = nil)
133
+ value ? Time.at(value.to_i) : nil
134
+ end
135
+ end
136
+
137
+ # Register and use the custom field type
138
+ class Event < Familia::Horreum
139
+ def self.timestamp_field(name, **options)
140
+ field_type = TimestampFieldType.new(name, **options)
141
+ register_field_type(field_type)
142
+ end
143
+
144
+ identifier_field :event_id
145
+ field :event_id, :name, :description
146
+ timestamp_field :created_at
147
+ timestamp_field :updated_at
148
+ end
149
+
150
+ # Usage
151
+ event = Event.new(event_id: 'evt_123')
152
+ event.created_at = "2023-06-15 14:30:00" # String input
153
+ puts event.created_at.class # => Time
154
+ puts event.created_at # => 2023-06-15 14:30:00 UTC
155
+ ```
156
+
157
+ ### JSON Field Type
158
+
159
+ ```ruby
160
+ class JsonFieldType < Familia::FieldType
161
+ def category
162
+ :json
163
+ end
164
+
165
+ def define_setter(klass)
166
+ field_name = @name
167
+ method_name = @method_name
168
+
169
+ handle_method_conflict(klass, :"#{method_name}=") do
170
+ klass.define_method :"#{method_name}=" do |value|
171
+ # Store as parsed JSON for manipulation
172
+ parsed_value = case value
173
+ when String then JSON.parse(value)
174
+ when Hash, Array then value
175
+ else raise ArgumentError, "Value must be JSON string, Hash, or Array"
176
+ end
177
+ instance_variable_set(:"@#{field_name}", parsed_value)
178
+ end
179
+ end
180
+ end
181
+
182
+ def serialize(value, _record = nil)
183
+ value.to_json if value
184
+ end
185
+
186
+ def deserialize(value, _record = nil)
187
+ value ? JSON.parse(value) : nil
188
+ end
189
+ end
190
+
191
+ class Configuration < Familia::Horreum
192
+ def self.json_field(name, **options)
193
+ field_type = JsonFieldType.new(name, **options)
194
+ register_field_type(field_type)
195
+ end
196
+
197
+ identifier_field :config_id
198
+ field :config_id, :name
199
+ json_field :settings
200
+ json_field :metadata
201
+ end
202
+
203
+ # Usage
204
+ config = Configuration.new(config_id: 'app_config')
205
+ config.settings = { theme: 'dark', notifications: true }
206
+ config.settings['api_timeout'] = 30
207
+
208
+ # Automatically serialized to JSON in database
209
+ config.save
210
+ # Database stores: {"theme":"dark","notifications":true,"api_timeout":30}
211
+ ```
212
+
213
+ ### Enum Field Type
214
+
215
+ ```ruby
216
+ class EnumFieldType < Familia::FieldType
217
+ def initialize(name, values:, **options)
218
+ super(name, **options)
219
+ @valid_values = values.map(&:to_s).to_set
220
+ @default_value = values.first
221
+ end
222
+
223
+ def category
224
+ :enum
225
+ end
226
+
227
+ def define_setter(klass)
228
+ field_name = @name
229
+ method_name = @method_name
230
+ valid_values = @valid_values
231
+
232
+ handle_method_conflict(klass, :"#{method_name}=") do
233
+ klass.define_method :"#{method_name}=" do |value|
234
+ value_str = value.to_s
235
+ unless valid_values.include?(value_str)
236
+ raise ArgumentError, "Invalid #{field_name}: #{value}. Valid values: #{valid_values.to_a.join(', ')}"
237
+ end
238
+ instance_variable_set(:"@#{field_name}", value_str)
239
+ end
240
+ end
241
+ end
242
+
243
+ # Add predicate methods for each enum value
244
+ def install(klass)
245
+ super(klass)
246
+
247
+ @valid_values.each do |value|
248
+ predicate_method = :"#{@method_name}_#{value}?"
249
+ field_name = @name
250
+
251
+ klass.define_method predicate_method do
252
+ instance_variable_get(:"@#{field_name}") == value
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ class Order < Familia::Horreum
259
+ def self.enum_field(name, values:, **options)
260
+ field_type = EnumFieldType.new(name, values: values, **options)
261
+ register_field_type(field_type)
262
+ end
263
+
264
+ identifier_field :order_id
265
+ field :order_id, :customer_id
266
+ enum_field :status, values: [:pending, :processing, :shipped, :delivered, :cancelled]
267
+ enum_field :priority, values: [:low, :normal, :high, :urgent]
268
+ end
269
+
270
+ # Usage
271
+ order = Order.new(order_id: 'ord_123')
272
+ order.status = :pending
273
+ order.priority = 'high'
274
+
275
+ # Predicate methods automatically available
276
+ order.status_pending? # => true
277
+ order.status_shipped? # => false
278
+ order.priority_high? # => true
279
+ order.priority_urgent? # => false
280
+ ```
281
+
282
+ ## Field Metadata and Introspection
283
+
284
+ ### Accessing Field Information
285
+
286
+ ```ruby
287
+ class Product < Familia::Horreum
288
+ field :name, category: :searchable
289
+ field :price, category: :numeric
290
+ field :description, category: :text
291
+ field :secret_key, category: :encrypted
292
+ transient_field :temp_data
293
+ end
294
+
295
+ # Get all field names
296
+ Product.fields
297
+ # => [:name, :price, :description, :secret_key, :temp_data]
298
+
299
+ # Get field types registry
300
+ Product.field_types
301
+ # => { name: #<FieldType...>, price: #<FieldType...>, ... }
302
+
303
+ # Get fields by category
304
+ Product.fields.select { |f| Product.field_types[f].category == :searchable }
305
+ # => [:name]
306
+
307
+ # Get persistent vs transient fields
308
+ Product.persistent_fields # => [:name, :price, :description, :secret_key]
309
+ Product.transient_fields # => [:temp_data]
310
+
311
+ # Field method mapping (for backward compatibility)
312
+ Product.field_method_map
313
+ # => { name: :name, price: :price, secret_key: :secret_key, temp_data: :temp_data }
314
+ ```
315
+
316
+ ### Field Categories for Feature Processing
317
+
318
+ ```ruby
319
+ # Features can process fields by category
320
+ module SearchableFieldsFeature
321
+ def self.included(base)
322
+ base.extend ClassMethods
323
+
324
+ # Process all searchable fields
325
+ searchable_fields = base.fields.select do |field|
326
+ base.field_types[field].category == :searchable
327
+ end
328
+
329
+ searchable_fields.each do |field|
330
+ create_search_index_for(base, field)
331
+ end
332
+ end
333
+
334
+ module ClassMethods
335
+ def search_by_field(field_name, query)
336
+ # Implementation for field-specific search
337
+ end
338
+ end
339
+
340
+ private
341
+
342
+ def self.create_search_index_for(klass, field_name)
343
+ # Create search index methods
344
+ klass.define_singleton_method :"search_by_#{field_name}" do |query|
345
+ # Search implementation
346
+ end
347
+ end
348
+ end
349
+
350
+ class Product < Familia::Horreum
351
+ feature :searchable_fields # Processes all :searchable category fields
352
+
353
+ field :name, category: :searchable
354
+ field :description, category: :searchable
355
+ field :internal_id, category: :system
356
+ end
357
+
358
+ # Auto-generated search methods available
359
+ Product.search_by_name("laptop")
360
+ Product.search_by_description("gaming")
361
+ ```
362
+
363
+ ## Fast Methods and Database Operations
364
+
365
+ ### Fast Method Behavior
366
+
367
+ Fast methods provide immediate database persistence without affecting other object state:
368
+
369
+ ```ruby
370
+ class UserProfile < Familia::Horreum
371
+ identifier_field :user_id
372
+ field :user_id, :name, :email, :last_login_at
373
+ end
374
+
375
+ profile = UserProfile.new(user_id: 'user_123')
376
+ profile.save
377
+
378
+ # Regular setter: updates instance variable only
379
+ profile.last_login_at = Time.now # Not yet in database
380
+
381
+ # Fast method: immediate database write
382
+ profile.last_login_at!(Time.now) # Written to database immediately
383
+
384
+ # Reading from database
385
+ profile.last_login_at # => reads from instance variable
386
+ profile.last_login_at! # => reads directly from database
387
+ ```
388
+
389
+ ### Custom Fast Method Behavior
390
+
391
+ ```ruby
392
+ class AuditedFieldType < Familia::FieldType
393
+ def define_fast_writer(klass)
394
+ return unless @fast_method_name
395
+
396
+ field_name = @name
397
+ method_name = @method_name
398
+ fast_method_name = @fast_method_name
399
+
400
+ handle_method_conflict(klass, fast_method_name) do
401
+ klass.define_method fast_method_name do |*args|
402
+ if args.empty?
403
+ # Read from database
404
+ hget(field_name)
405
+ else
406
+ # Write to database with audit trail
407
+ value = args.first
408
+ old_value = hget(field_name)
409
+
410
+ # Update the field
411
+ prepared = serialize_value(value)
412
+ send(:"#{method_name}=", value) if method_name
413
+ result = hset(field_name, prepared)
414
+
415
+ # Create audit entry
416
+ audit_entry = {
417
+ field: field_name,
418
+ old_value: old_value,
419
+ new_value: value,
420
+ changed_at: Time.now.to_f,
421
+ changed_by: Thread.current[:current_user]&.id
422
+ }
423
+
424
+ # Store audit trail
425
+ audit_key = "#{dbkey}:audit"
426
+ Familia.dbclient.lpush(audit_key, audit_entry.to_json)
427
+ Familia.dbclient.ltrim(audit_key, 0, 99) # Keep last 100 changes
428
+
429
+ result
430
+ end
431
+ end
432
+ end
433
+ end
434
+ end
435
+
436
+ class AuditedDocument < Familia::Horreum
437
+ def self.audited_field(name, **options)
438
+ field_type = AuditedFieldType.new(name, **options)
439
+ register_field_type(field_type)
440
+ end
441
+
442
+ identifier_field :doc_id
443
+ field :doc_id, :title
444
+ audited_field :content
445
+ audited_field :status
446
+ end
447
+
448
+ # Usage creates audit trail
449
+ doc = AuditedDocument.new(doc_id: 'doc_123')
450
+ doc.save
451
+
452
+ Thread.current[:current_user] = OpenStruct.new(id: 'user_456')
453
+ doc.content!("Initial content") # Audited change
454
+ doc.status!("draft") # Audited change
455
+
456
+ # View audit trail
457
+ audit_key = "#{doc.dbkey}:audit"
458
+ audit_entries = Familia.dbclient.lrange(audit_key, 0, -1)
459
+ audit_entries.map { |entry| JSON.parse(entry) }
460
+ ```
461
+
462
+ ## Integration Patterns
463
+
464
+ ### Rails Integration
465
+
466
+ ```ruby
467
+ # app/models/concerns/familia_fields.rb
468
+ module FamiliaFields
469
+ extend ActiveSupport::Concern
470
+
471
+ class_methods do
472
+ # Rails-style field definitions
473
+ def string_field(name, **options)
474
+ field(name, **options)
475
+ end
476
+
477
+ def integer_field(name, **options)
478
+ field_type = Class.new(Familia::FieldType) do
479
+ def serialize(value, _record = nil)
480
+ value.to_i if value
481
+ end
482
+
483
+ def deserialize(value, _record = nil)
484
+ value.to_i if value
485
+ end
486
+ end
487
+
488
+ register_field_type(field_type.new(name, **options))
489
+ end
490
+
491
+ def boolean_field(name, **options)
492
+ field_type = Class.new(Familia::FieldType) do
493
+ def serialize(value, _record = nil)
494
+ !!value
495
+ end
496
+
497
+ def deserialize(value, _record = nil)
498
+ value == true || value == 'true' || value == '1'
499
+ end
500
+
501
+ def define_getter(klass)
502
+ super(klass)
503
+
504
+ # Add predicate method
505
+ predicate_method = :"#{@method_name}?"
506
+ field_name = @name
507
+
508
+ klass.define_method predicate_method do
509
+ !!instance_variable_get(:"@#{field_name}")
510
+ end
511
+ end
512
+ end
513
+
514
+ register_field_type(field_type.new(name, **options))
515
+ end
516
+ end
517
+ end
518
+
519
+ class User < Familia::Horreum
520
+ include FamiliaFields
521
+
522
+ identifier_field :user_id
523
+ string_field :user_id, :email, :name
524
+ integer_field :age, :login_count
525
+ boolean_field :active, :verified
526
+ end
527
+
528
+ user = User.new(user_id: 'user_123')
529
+ user.age = "25" # Automatically converted to integer
530
+ user.active = "true" # Automatically converted to boolean
531
+ user.verified? # => false (predicate method)
532
+ ```
533
+
534
+ ### Validation Integration
535
+
536
+ ```ruby
537
+ class ValidatedFieldType < Familia::FieldType
538
+ def initialize(name, validations: {}, **options)
539
+ super(name, **options)
540
+ @validations = validations
541
+ end
542
+
543
+ def define_setter(klass)
544
+ field_name = @name
545
+ method_name = @method_name
546
+ validations = @validations
547
+
548
+ handle_method_conflict(klass, :"#{method_name}=") do
549
+ klass.define_method :"#{method_name}=" do |value|
550
+ # Run validations
551
+ validations.each do |validator, constraint|
552
+ case validator
553
+ when :presence
554
+ if constraint && (value.nil? || value.to_s.strip.empty?)
555
+ raise ArgumentError, "#{field_name} cannot be blank"
556
+ end
557
+ when :length
558
+ if constraint.is_a?(Hash) && constraint[:minimum]
559
+ if value.to_s.length < constraint[:minimum]
560
+ raise ArgumentError, "#{field_name} is too short (minimum #{constraint[:minimum]} characters)"
561
+ end
562
+ end
563
+ when :format
564
+ if constraint.is_a?(Regexp) && !value.to_s.match?(constraint)
565
+ raise ArgumentError, "#{field_name} format is invalid"
566
+ end
567
+ when :inclusion
568
+ if constraint.is_a?(Array) && !constraint.include?(value)
569
+ raise ArgumentError, "#{field_name} must be one of: #{constraint.join(', ')}"
570
+ end
571
+ end
572
+ end
573
+
574
+ instance_variable_set(:"@#{field_name}", value)
575
+ end
576
+ end
577
+ end
578
+ end
579
+
580
+ class User < Familia::Horreum
581
+ def self.validated_field(name, validations: {}, **options)
582
+ field_type = ValidatedFieldType.new(name, validations: validations, **options)
583
+ register_field_type(field_type)
584
+ end
585
+
586
+ identifier_field :user_id
587
+ validated_field :user_id, validations: { presence: true }
588
+ validated_field :email, validations: {
589
+ presence: true,
590
+ format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
591
+ }
592
+ validated_field :status, validations: {
593
+ inclusion: %w[active inactive suspended]
594
+ }
595
+ validated_field :name, validations: {
596
+ presence: true,
597
+ length: { minimum: 2 }
598
+ }
599
+ end
600
+
601
+ # Usage with validation
602
+ user = User.new
603
+ user.email = "invalid-email" # Raises ArgumentError
604
+ user.status = "unknown" # Raises ArgumentError
605
+ user.name = "A" # Raises ArgumentError (too short)
606
+ ```
607
+
608
+ ## Performance Considerations
609
+
610
+ ### Efficient Field Operations
611
+
612
+ ```ruby
613
+ class OptimizedFieldAccess < Familia::Horreum
614
+ # Cache field type lookups
615
+ def self.field_type_for(field_name)
616
+ @field_type_cache ||= {}
617
+ @field_type_cache[field_name] ||= field_types[field_name]
618
+ end
619
+
620
+ # Batch field updates
621
+ def batch_update(field_values)
622
+ # Update instance variables
623
+ field_values.each do |field, value|
624
+ setter_method = :"#{field}="
625
+ send(setter_method, value) if respond_to?(setter_method)
626
+ end
627
+
628
+ # Single database call for persistence
629
+ serialized_values = field_values.transform_values do |value|
630
+ serialize_value(value)
631
+ end
632
+
633
+ hmset(serialized_values)
634
+ end
635
+
636
+ # Lazy field loading
637
+ def lazy_load_field(field_name)
638
+ return instance_variable_get(:"@#{field_name}") if instance_variable_defined?(:"@#{field_name}")
639
+
640
+ value = hget(field_name)
641
+ field_type = self.class.field_type_for(field_name)
642
+ deserialized = field_type&.deserialize(value, self) || value
643
+
644
+ instance_variable_set(:"@#{field_name}", deserialized)
645
+ deserialized
646
+ end
647
+ end
648
+ ```
649
+
650
+ ### Memory-Efficient Field Storage
651
+
652
+ ```ruby
653
+ class CompactFieldType < Familia::FieldType
654
+ def serialize(value, _record = nil)
655
+ case value
656
+ when String
657
+ # Compress strings longer than 100 characters
658
+ if value.length > 100
659
+ Base64.encode64(Zlib::Deflate.deflate(value))
660
+ else
661
+ value
662
+ end
663
+ else
664
+ value
665
+ end
666
+ end
667
+
668
+ def deserialize(value, _record = nil)
669
+ return value unless value.is_a?(String)
670
+
671
+ # Check if it's base64 encoded compressed data
672
+ if value.length > 100 && value.match?(/\A[A-Za-z0-9+\/]*={0,2}\z/)
673
+ begin
674
+ Zlib::Inflate.inflate(Base64.decode64(value))
675
+ rescue
676
+ value # Return as-is if decompression fails
677
+ end
678
+ else
679
+ value
680
+ end
681
+ end
682
+ end
683
+ ```
684
+
685
+ ## Testing Field Types
686
+
687
+ ### RSpec Testing
688
+
689
+ ```ruby
690
+ RSpec.describe TimestampFieldType do
691
+ let(:field_type) { described_class.new(:created_at) }
692
+ let(:test_class) do
693
+ Class.new(Familia::Horreum) do
694
+ def self.name; 'TestClass'; end
695
+ end
696
+ end
697
+
698
+ before do
699
+ field_type.install(test_class)
700
+ end
701
+
702
+ it "converts various time formats" do
703
+ instance = test_class.new
704
+
705
+ instance.created_at = "2023-06-15 14:30:00"
706
+ expect(instance.created_at).to be_a(Time)
707
+
708
+ instance.created_at = Time.now
709
+ expect(instance.created_at).to be_a(Time)
710
+
711
+ instance.created_at = Time.now.to_i
712
+ expect(instance.created_at).to be_a(Time)
713
+ end
714
+
715
+ it "serializes to integer" do
716
+ time_value = Time.now
717
+ serialized = field_type.serialize(time_value)
718
+ expect(serialized).to be_a(Integer)
719
+ expect(serialized).to eq(time_value.to_i)
720
+ end
721
+
722
+ it "deserializes from integer" do
723
+ timestamp = Time.now.to_i
724
+ deserialized = field_type.deserialize(timestamp)
725
+ expect(deserialized).to be_a(Time)
726
+ expect(deserialized.to_i).to eq(timestamp)
727
+ end
728
+ end
729
+ ```
730
+
731
+ ## Best Practices
732
+
733
+ ### 1. Choose Appropriate Field Types
734
+
735
+ ```ruby
736
+ # Use built-in field types when possible
737
+ class User < Familia::Horreum
738
+ field :name # Simple string field
739
+ field :metadata, category: :json # For complex data
740
+ transient_field :temp_token # For runtime-only data
741
+ encrypted_field :api_key # For sensitive data
742
+ end
743
+
744
+ # Create custom types for specialized behavior
745
+ class GeoLocation < Familia::Horreum
746
+ coordinate_field :latitude # Custom validation and formatting
747
+ coordinate_field :longitude
748
+ end
749
+ ```
750
+
751
+ ### 2. Handle Method Conflicts Gracefully
752
+
753
+ ```ruby
754
+ class SafeFieldDefinition < Familia::Horreum
755
+ # Check for conflicts before defining fields
756
+ def self.safe_field(name, **options)
757
+ if method_defined?(name) || method_defined?(:"#{name}=")
758
+ Rails.logger.warn "Method conflict for field #{name}, using alternative name"
759
+ options[:as] = :"#{name}_value"
760
+ end
761
+
762
+ field(name, **options)
763
+ end
764
+ end
765
+ ```
766
+
767
+ ### 3. Optimize for Common Use Cases
768
+
769
+ ```ruby
770
+ # Provide convenience methods for common patterns
771
+ class BaseModel < Familia::Horreum
772
+ def self.timestamps
773
+ timestamp_field :created_at, as: :created_at
774
+ timestamp_field :updated_at, as: :updated_at
775
+ end
776
+
777
+ def self.soft_delete
778
+ boolean_field :deleted, as: :deleted
779
+ timestamp_field :deleted_at, as: :deleted_at
780
+ end
781
+ end
782
+ ```
783
+
784
+ The Field System provides a powerful foundation for defining flexible, extensible object attributes with customizable behavior, validation, and serialization capabilities.