familia 2.0.0.pre17 → 2.0.0.pre19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.rst +118 -6
- data/CLAUDE.md +43 -11
- data/Gemfile +2 -2
- data/Gemfile.lock +9 -47
- data/README.md +52 -0
- data/bin/irb +1 -1
- data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +91 -0
- data/changelog.d/20251011_203905_delano_next.rst +30 -0
- data/changelog.d/20251011_212633_delano_next.rst +13 -0
- data/changelog.d/20251011_221253_delano_next.rst +26 -0
- data/docs/guides/core-field-system.md +48 -26
- data/docs/guides/feature-expiration.md +18 -18
- data/docs/migrating/v2.0.0-pre18.md +58 -0
- data/docs/migrating/v2.0.0-pre19.md +197 -0
- data/docs/qodo-merge-compliance.md +96 -0
- data/examples/datatype_standalone.rb +281 -0
- data/lib/familia/base.rb +0 -2
- data/lib/familia/connection/behavior.rb +252 -0
- data/lib/familia/connection/handlers.rb +95 -0
- data/lib/familia/connection/middleware.rb +58 -4
- data/lib/familia/connection/operation_core.rb +1 -1
- data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +2 -2
- data/lib/familia/connection/transaction_core.rb +7 -9
- data/lib/familia/connection.rb +2 -1
- data/lib/familia/data_type/connection.rb +151 -7
- data/lib/familia/data_type/{commands.rb → database_commands.rb} +9 -6
- data/lib/familia/data_type/serialization.rb +9 -5
- data/lib/familia/data_type/types/hashkey.rb +1 -1
- data/lib/familia/data_type.rb +2 -2
- data/lib/familia/encryption/encrypted_data.rb +12 -2
- data/lib/familia/encryption/manager.rb +11 -4
- data/lib/familia/errors.rb +51 -14
- data/lib/familia/features/autoloader.rb +3 -1
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
- data/lib/familia/features/expiration/extensions.rb +8 -10
- data/lib/familia/features/expiration.rb +19 -19
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +45 -44
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +151 -65
- data/lib/familia/features/relationships/indexing.rb +37 -42
- data/lib/familia/features/relationships/indexing_relationship.rb +14 -4
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/field_type.rb +2 -1
- data/lib/familia/horreum/connection.rb +11 -35
- data/lib/familia/horreum/database_commands.rb +130 -11
- data/lib/familia/horreum/definition.rb +8 -38
- data/lib/familia/horreum/management.rb +38 -27
- data/lib/familia/horreum/persistence.rb +191 -67
- data/lib/familia/horreum/serialization.rb +94 -73
- data/lib/familia/horreum/utils.rb +0 -8
- data/lib/familia/horreum.rb +41 -18
- data/lib/familia/identifier_extractor.rb +60 -0
- data/lib/familia/logging.rb +268 -112
- data/lib/familia/refinements.rb +0 -1
- data/lib/familia/settings.rb +7 -7
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -2
- data/lib/middleware/{database_middleware.rb → database_logger.rb} +118 -14
- data/pr_agent.toml +31 -0
- data/pr_compliance_checklist.yaml +45 -0
- data/try/edge_cases/empty_identifiers_try.rb +1 -1
- data/try/edge_cases/hash_symbolization_try.rb +31 -31
- data/try/edge_cases/json_serialization_try.rb +2 -2
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
- data/try/edge_cases/race_conditions_try.rb +1 -1
- data/try/edge_cases/reserved_keywords_try.rb +1 -1
- data/try/edge_cases/string_coercion_try.rb +5 -5
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
- data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
- data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
- data/try/features/encrypted_fields/memory_security_try.rb +1 -1
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
- data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
- data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
- data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
- data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
- data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
- data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
- data/try/features/expiration/expiration_try.rb +2 -2
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +1 -1
- data/try/features/feature_improvements_try.rb +1 -1
- data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
- data/try/features/object_identifier/object_identifier_try.rb +1 -1
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/real_feature_integration_try.rb +17 -14
- data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
- data/try/features/relationships/indexing_try.rb +34 -5
- data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
- data/try/features/relationships/participation_commands_verification_try.rb +4 -4
- data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
- data/try/features/relationships/participation_reverse_index_try.rb +1 -1
- data/try/features/relationships/relationships_api_changes_try.rb +5 -5
- data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
- data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
- data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
- data/try/features/relationships/relationships_performance_try.rb +1 -1
- data/try/features/relationships/relationships_performance_working_try.rb +1 -1
- data/try/features/relationships/relationships_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_try.rb +1 -1
- data/try/features/transient_fields/redacted_string_try.rb +1 -1
- data/try/features/transient_fields/refresh_reset_try.rb +1 -1
- data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +4 -4
- data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
- data/try/{core → integration/connection}/isolated_dbclient_try.rb +1 -1
- data/try/integration/connection/middleware_reconnect_try.rb +87 -0
- data/try/{connection → integration/connection}/operation_mode_guards_try.rb +2 -2
- data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +13 -13
- data/try/{core → integration/connection}/pools_try.rb +1 -1
- data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
- data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
- data/try/{core → integration}/create_method_try.rb +23 -23
- data/try/integration/cross_component_try.rb +1 -1
- data/try/integration/data_types/datatype_pipelines_try.rb +104 -0
- data/try/integration/data_types/datatype_transactions_try.rb +247 -0
- data/try/{core → integration}/database_consistency_try.rb +11 -8
- data/try/{core → integration}/familia_extended_try.rb +1 -1
- data/try/{core → integration}/familia_members_methods_try.rb +1 -1
- data/try/{models → integration/models}/customer_safe_dump_try.rb +6 -2
- data/try/{models → integration/models}/customer_try.rb +1 -1
- data/try/{models → integration/models}/datatype_base_try.rb +1 -1
- data/try/{models → integration/models}/familia_object_try.rb +2 -2
- data/try/{core → integration}/persistence_operations_try.rb +163 -11
- data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
- data/try/{configuration → integration}/scenarios_try.rb +1 -1
- data/try/{core → integration}/secure_identifier_try.rb +1 -1
- data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
- data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
- data/try/{helpers → support/helpers}/test_helpers.rb +12 -3
- data/try/{core → unit/core}/autoloader_try.rb +1 -1
- data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
- data/try/{core → unit/core}/connection_try.rb +1 -1
- data/try/{core → unit/core}/errors_try.rb +1 -1
- data/try/{core → unit/core}/extensions_try.rb +1 -1
- data/try/unit/core/familia_logger_try.rb +110 -0
- data/try/{core → unit/core}/familia_try.rb +1 -1
- data/try/{core → unit/core}/middleware_try.rb +41 -1
- data/try/{core → unit/core}/settings_try.rb +1 -1
- data/try/{core → unit/core}/time_utils_try.rb +1 -1
- data/try/{core → unit/core}/tools_try.rb +1 -1
- data/try/{core → unit/core}/utils_try.rb +17 -14
- data/try/{data_types → unit/data_types}/boolean_try.rb +2 -2
- data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
- data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
- data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
- data/try/{data_types → unit/data_types}/list_try.rb +1 -1
- data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
- data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
- data/try/{data_types → unit/data_types}/sorted_set_zadd_options_try.rb +1 -1
- data/try/{data_types → unit/data_types}/string_try.rb +2 -2
- data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
- data/try/{horreum → unit/horreum}/auto_indexing_on_save_try.rb +33 -17
- data/try/unit/horreum/automatic_index_validation_try.rb +253 -0
- data/try/{horreum → unit/horreum}/base_try.rb +4 -4
- data/try/{horreum → unit/horreum}/class_methods_try.rb +3 -3
- data/try/{horreum → unit/horreum}/commands_try.rb +1 -1
- data/try/{horreum → unit/horreum}/defensive_initialization_try.rb +1 -1
- data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +1 -1
- data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
- data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
- data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
- data/try/{horreum → unit/horreum}/initialization_try.rb +3 -3
- data/try/unit/horreum/json_type_preservation_try.rb +248 -0
- data/try/{horreum → unit/horreum}/relations_try.rb +5 -5
- data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
- data/try/{horreum → unit/horreum}/serialization_try.rb +6 -6
- data/try/{horreum → unit/horreum}/settings_try.rb +1 -1
- data/try/unit/horreum/unique_index_edge_cases_try.rb +376 -0
- data/try/unit/horreum/unique_index_guard_validation_try.rb +281 -0
- data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
- metadata +147 -126
- data/lib/familia/distinguisher.rb +0 -85
- data/lib/familia/refinements/logger_trace.rb +0 -60
- data/try/refinements/logger_trace_methods_try.rb +0 -44
- /data/try/{debugging → support/debugging}/README.md +0 -0
- /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
- /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
- /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
- /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +0 -0
- /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
- /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
@@ -50,17 +50,20 @@ customer.email = "admin@acme.com" # Custom method name
|
|
50
50
|
customer.name!("Updated Corp") # Fast writer (immediate DB persistence)
|
51
51
|
```
|
52
52
|
|
53
|
-
### Field
|
53
|
+
### Special Field Types
|
54
54
|
|
55
|
-
|
55
|
+
Use dedicated field methods provided by features for special field behaviors:
|
56
56
|
|
57
57
|
```ruby
|
58
58
|
class Document < Familia::Horreum
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
field :
|
63
|
-
|
59
|
+
feature :encrypted_fields
|
60
|
+
feature :transient_fields
|
61
|
+
|
62
|
+
field :title # Regular persistent field
|
63
|
+
encrypted_field :content # Encrypted storage
|
64
|
+
transient_field :api_key # Non-persistent (memory only)
|
65
|
+
field :tags # Regular field
|
66
|
+
field :metadata # Regular field
|
64
67
|
end
|
65
68
|
```
|
66
69
|
|
@@ -285,27 +288,28 @@ order.priority_urgent? # => false
|
|
285
288
|
|
286
289
|
```ruby
|
287
290
|
class Product < Familia::Horreum
|
288
|
-
|
289
|
-
|
290
|
-
field :
|
291
|
-
field :
|
291
|
+
feature :transient_fields
|
292
|
+
|
293
|
+
field :name
|
294
|
+
field :price
|
295
|
+
field :description
|
292
296
|
transient_field :temp_data
|
293
297
|
end
|
294
298
|
|
295
299
|
# Get all field names
|
296
300
|
Product.fields
|
297
|
-
# => [:name, :price, :description, :
|
301
|
+
# => [:name, :price, :description, :temp_data]
|
298
302
|
|
299
303
|
# Get field types registry
|
300
304
|
Product.field_types
|
301
305
|
# => { name: #<FieldType...>, price: #<FieldType...>, ... }
|
302
306
|
|
303
|
-
# Get fields by category
|
304
|
-
Product.fields.select { |f| Product.field_types[f].category == :
|
305
|
-
# => [:
|
307
|
+
# Get fields by category (read-only introspection)
|
308
|
+
Product.fields.select { |f| Product.field_types[f].category == :transient }
|
309
|
+
# => [:temp_data]
|
306
310
|
|
307
311
|
# Get persistent vs transient fields
|
308
|
-
Product.persistent_fields # => [:name, :price, :description
|
312
|
+
Product.persistent_fields # => [:name, :price, :description]
|
309
313
|
Product.transient_fields # => [:temp_data]
|
310
314
|
|
311
315
|
# Field method mapping (for backward compatibility)
|
@@ -313,15 +317,24 @@ Product.field_method_map
|
|
313
317
|
# => { name: :name, price: :price, secret_key: :secret_key, temp_data: :temp_data }
|
314
318
|
```
|
315
319
|
|
316
|
-
### Field
|
320
|
+
### Using Field Type Category for Introspection
|
321
|
+
|
322
|
+
The `category` method on FieldType provides read-only metadata for introspection:
|
317
323
|
|
318
324
|
```ruby
|
319
|
-
#
|
325
|
+
# Custom field type with category metadata
|
326
|
+
class SearchableFieldType < Familia::FieldType
|
327
|
+
def category
|
328
|
+
:searchable
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Features can process fields by inspecting their category
|
320
333
|
module SearchableFieldsFeature
|
321
334
|
def self.included(base)
|
322
335
|
base.extend ClassMethods
|
323
336
|
|
324
|
-
#
|
337
|
+
# Find all searchable fields by inspecting field type category
|
325
338
|
searchable_fields = base.fields.select do |field|
|
326
339
|
base.field_types[field].category == :searchable
|
327
340
|
end
|
@@ -348,11 +361,17 @@ module SearchableFieldsFeature
|
|
348
361
|
end
|
349
362
|
|
350
363
|
class Product < Familia::Horreum
|
351
|
-
feature :searchable_fields
|
364
|
+
feature :searchable_fields
|
352
365
|
|
353
|
-
field
|
354
|
-
|
355
|
-
|
366
|
+
# Use custom field type with searchable category
|
367
|
+
def self.searchable_field(name, **options)
|
368
|
+
field_type = SearchableFieldType.new(name, **options)
|
369
|
+
register_field_type(field_type)
|
370
|
+
end
|
371
|
+
|
372
|
+
searchable_field :name
|
373
|
+
searchable_field :description
|
374
|
+
field :internal_id
|
356
375
|
end
|
357
376
|
|
358
377
|
# Auto-generated search methods available
|
@@ -733,10 +752,13 @@ end
|
|
733
752
|
### 1. Choose Appropriate Field Types
|
734
753
|
|
735
754
|
```ruby
|
736
|
-
# Use
|
755
|
+
# Use dedicated field methods provided by features
|
737
756
|
class User < Familia::Horreum
|
738
|
-
|
739
|
-
|
757
|
+
feature :transient_fields
|
758
|
+
feature :encrypted_fields
|
759
|
+
|
760
|
+
field :name # Simple persistent field
|
761
|
+
field :metadata # For complex data
|
740
762
|
transient_field :temp_token # For runtime-only data
|
741
763
|
encrypted_field :api_key # For sensitive data
|
742
764
|
end
|
@@ -66,7 +66,7 @@ session.default_expiration # => 900.0
|
|
66
66
|
session.update_expiration # Uses instance expiration (15 minutes)
|
67
67
|
|
68
68
|
# Or specify expiration inline
|
69
|
-
session.update_expiration(
|
69
|
+
session.update_expiration(expiration: 5.minutes)
|
70
70
|
```
|
71
71
|
|
72
72
|
## Advanced Usage
|
@@ -112,7 +112,7 @@ customer = Customer.new(customer_id: 'cust_123')
|
|
112
112
|
customer.save
|
113
113
|
|
114
114
|
# This will set TTL on the main object AND all related fields
|
115
|
-
customer.update_expiration(
|
115
|
+
customer.update_expiration(expiration: 12.hours)
|
116
116
|
# Sets expiration on:
|
117
117
|
# - customer:cust_123 (main hash)
|
118
118
|
# - customer:cust_123:recent_orders (list)
|
@@ -137,9 +137,9 @@ class AnalyticsEvent < Familia::Horreum
|
|
137
137
|
save
|
138
138
|
|
139
139
|
if should_expire?
|
140
|
-
update_expiration(
|
140
|
+
update_expiration(expiration: 1.hour)
|
141
141
|
else
|
142
|
-
update_expiration(
|
142
|
+
update_expiration(expiration: 30.days)
|
143
143
|
end
|
144
144
|
end
|
145
145
|
end
|
@@ -200,7 +200,7 @@ class SessionCleanupJob
|
|
200
200
|
# Extend expiration for active sessions
|
201
201
|
UserSession.all.each do |session|
|
202
202
|
if session.recently_active?
|
203
|
-
session.update_expiration(
|
203
|
+
session.update_expiration(expiration: 30.minutes)
|
204
204
|
end
|
205
205
|
end
|
206
206
|
end
|
@@ -225,7 +225,7 @@ class SessionExpirationMiddleware
|
|
225
225
|
session = UserSession.find(session_token)
|
226
226
|
|
227
227
|
# Extend session TTL on each request
|
228
|
-
session&.update_expiration(
|
228
|
+
session&.update_expiration(expiration: 30.minutes)
|
229
229
|
end
|
230
230
|
|
231
231
|
@app.call(env)
|
@@ -268,14 +268,14 @@ end
|
|
268
268
|
class SessionManager
|
269
269
|
def self.extend_all_sessions(new_ttl)
|
270
270
|
UserSession.all.each do |session|
|
271
|
-
session.update_expiration(
|
271
|
+
session.update_expiration(expiration: new_ttl)
|
272
272
|
end
|
273
273
|
end
|
274
274
|
|
275
275
|
def self.expire_inactive_sessions
|
276
276
|
UserSession.all.select(&:inactive?).each do |session|
|
277
277
|
# Set very short TTL for inactive sessions
|
278
|
-
session.update_expiration(
|
278
|
+
session.update_expiration(expiration: 5.minutes)
|
279
279
|
end
|
280
280
|
end
|
281
281
|
|
@@ -306,7 +306,7 @@ class DataRetentionService
|
|
306
306
|
model_class = data_type.to_s.pascalize.constantize
|
307
307
|
|
308
308
|
model_class.all.each do |record|
|
309
|
-
record.update_expiration(
|
309
|
+
record.update_expiration(expiration: ttl)
|
310
310
|
end
|
311
311
|
end
|
312
312
|
end
|
@@ -323,7 +323,7 @@ DataRetentionService.apply_retention_policies
|
|
323
323
|
```ruby
|
324
324
|
# ❌ Inefficient: Multiple round trips
|
325
325
|
sessions.each do |session|
|
326
|
-
session.update_expiration(
|
326
|
+
session.update_expiration(expiration: 1.hour)
|
327
327
|
end
|
328
328
|
|
329
329
|
# ✅ Efficient: Batch operations
|
@@ -357,7 +357,7 @@ class ResilientSession < Familia::Horreum
|
|
357
357
|
return unless exists?
|
358
358
|
|
359
359
|
begin
|
360
|
-
update_expiration(
|
360
|
+
update_expiration(expiration: new_ttl)
|
361
361
|
rescue => e
|
362
362
|
# Log error but don't crash the application
|
363
363
|
Familia.logger.warn "Failed to update expiration for #{dbkey}: #{e.message}"
|
@@ -377,7 +377,7 @@ Familia.debug = true
|
|
377
377
|
|
378
378
|
session = UserSession.new(session_token: 'debug_session')
|
379
379
|
session.save
|
380
|
-
session.update_expiration(
|
380
|
+
session.update_expiration(expiration: 5.minutes)
|
381
381
|
# Logs will show:
|
382
382
|
# [update_expiration] Expires session:debug_session in 300.0 seconds
|
383
383
|
```
|
@@ -388,11 +388,11 @@ session.update_expiration(default_expiration: 5.minutes)
|
|
388
388
|
```ruby
|
389
389
|
session = UserSession.new
|
390
390
|
# ❌ Won't work - object must be saved first
|
391
|
-
session.update_expiration(
|
391
|
+
session.update_expiration(expiration: 1.hour)
|
392
392
|
|
393
393
|
# ✅ Correct - save first, then expire
|
394
394
|
session.save
|
395
|
-
session.update_expiration(
|
395
|
+
session.update_expiration(expiration: 1.hour)
|
396
396
|
```
|
397
397
|
|
398
398
|
**2. Related Fields Not Expiring**
|
@@ -459,7 +459,7 @@ RSpec.describe UserSession do
|
|
459
459
|
|
460
460
|
it "applies TTL to database key" do
|
461
461
|
session.save
|
462
|
-
session.update_expiration(
|
462
|
+
session.update_expiration(expiration: 10.minutes)
|
463
463
|
|
464
464
|
ttl = session.ttl
|
465
465
|
expect(ttl).to be > 500 # Should be close to 600 seconds
|
@@ -470,7 +470,7 @@ RSpec.describe UserSession do
|
|
470
470
|
session.save
|
471
471
|
session.activity_log.push('login') # Assume activity_log is a list
|
472
472
|
|
473
|
-
session.update_expiration(
|
473
|
+
session.update_expiration(expiration: 5.minutes)
|
474
474
|
|
475
475
|
# Both main object and related fields should have TTL
|
476
476
|
expect(session.ttl).to be > 250
|
@@ -542,7 +542,7 @@ class TTLHealthCheck
|
|
542
542
|
expired_count += 1
|
543
543
|
elsif ttl < 300 # Less than 5 minutes remaining
|
544
544
|
# Extend TTL for active sessions
|
545
|
-
session.update_expiration(
|
545
|
+
session.update_expiration(expiration: 30.minutes) if session.active?
|
546
546
|
end
|
547
547
|
end
|
548
548
|
|
@@ -565,7 +565,7 @@ class RobustSessionManager
|
|
565
565
|
# Check if session exists and hasn't expired
|
566
566
|
if session&.ttl&.positive?
|
567
567
|
# Extend TTL on access
|
568
|
-
session.update_expiration(
|
568
|
+
session.update_expiration(expiration: 30.minutes)
|
569
569
|
session
|
570
570
|
else
|
571
571
|
# Create new session if old one expired
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Migrating Guide: v2.0.0-pre18
|
2
|
+
|
3
|
+
This version completes the JSON serialization implementation by removing the string-as-is optimization and fixes encrypted field visibility in serialization.
|
4
|
+
|
5
|
+
## JSON Serialization for All Types
|
6
|
+
|
7
|
+
**What Changed:**
|
8
|
+
|
9
|
+
All field values (including strings) are now JSON-encoded during storage for consistent type preservation.
|
10
|
+
|
11
|
+
**Storage Format:**
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
# Before (v2.0.0-pre14):
|
15
|
+
HGET user:123 name
|
16
|
+
"John Doe" # Plain string
|
17
|
+
|
18
|
+
# After (v2.0.0-pre18):
|
19
|
+
HGET user:123 name
|
20
|
+
"\"John Doe\"" # JSON-encoded string
|
21
|
+
```
|
22
|
+
|
23
|
+
**Migration:**
|
24
|
+
|
25
|
+
No migration needed. The deserializer automatically handles:
|
26
|
+
- **New format**: `"\"value\""` → `"value"`
|
27
|
+
- **Legacy format**: `"value"` → `"value"`
|
28
|
+
|
29
|
+
**Why This Matters:**
|
30
|
+
|
31
|
+
Prevents data corruption for edge cases:
|
32
|
+
- Badge `"007"` stays `"007"` (not converted to integer `7`)
|
33
|
+
- String `"true"` stays `"true"` (not converted to boolean)
|
34
|
+
- String `"null"` stays `"null"` (not converted to `nil`)
|
35
|
+
|
36
|
+
## Encrypted Field Security Fix
|
37
|
+
|
38
|
+
**What Changed:**
|
39
|
+
|
40
|
+
Fields defined with `category: :encrypted` now correctly exclude encrypted data from `to_h()` output.
|
41
|
+
|
42
|
+
**Before:**
|
43
|
+
```ruby
|
44
|
+
field :secret, category: :encrypted
|
45
|
+
user.to_h # => {"id" => "123", "secret" => {...}} # ❌ Exposed!
|
46
|
+
```
|
47
|
+
|
48
|
+
**After:**
|
49
|
+
```ruby
|
50
|
+
field :secret, category: :encrypted
|
51
|
+
user.to_h # => {"id" => "123"} # ✅ Secure
|
52
|
+
```
|
53
|
+
|
54
|
+
Both `encrypted_field` and `field :name, category: :encrypted` now behave identically for security.
|
55
|
+
|
56
|
+
**Migration:**
|
57
|
+
|
58
|
+
No code changes needed. Review any code relying on encrypted fields appearing in `to_h()` output.
|
@@ -0,0 +1,197 @@
|
|
1
|
+
# Migrating Guide: v2.0.0-pre19
|
2
|
+
|
3
|
+
This version introduces significant improvements to Familia's database operations, making them more atomic, reliable, and consistent with Rails conventions. The changes include enhanced error handling, optimistic locking support, and breaking API changes.
|
4
|
+
|
5
|
+
## Breaking Changes
|
6
|
+
|
7
|
+
### Management.create → create!
|
8
|
+
|
9
|
+
**What Changed:**
|
10
|
+
|
11
|
+
The `create` class method has been renamed to `create!` to follow Rails conventions and indicate that it raises exceptions on failure.
|
12
|
+
|
13
|
+
**Before:**
|
14
|
+
```ruby
|
15
|
+
user = User.create(email: "test@example.com")
|
16
|
+
```
|
17
|
+
|
18
|
+
**After:**
|
19
|
+
```ruby
|
20
|
+
user = User.create!(email: "test@example.com")
|
21
|
+
```
|
22
|
+
|
23
|
+
**Why This Matters:**
|
24
|
+
|
25
|
+
- Follows Rails naming conventions where `!` methods raise exceptions
|
26
|
+
- Makes it clear that `CreationError` will be raised if object already exists
|
27
|
+
- Prevents silent failures in object creation
|
28
|
+
|
29
|
+
### save_if_not_exists → save_if_not_exists!
|
30
|
+
|
31
|
+
**What Changed:**
|
32
|
+
|
33
|
+
The `save_if_not_exists` method has been renamed to `save_if_not_exists!` and now includes optimistic locking with automatic retry logic.
|
34
|
+
|
35
|
+
**Before:**
|
36
|
+
```ruby
|
37
|
+
success = user.save_if_not_exists
|
38
|
+
```
|
39
|
+
|
40
|
+
**After:**
|
41
|
+
```ruby
|
42
|
+
success = user.save_if_not_exists!
|
43
|
+
```
|
44
|
+
|
45
|
+
**Behavior Changes:**
|
46
|
+
- Now uses Redis WATCH/MULTI/EXEC for optimistic locking
|
47
|
+
- Automatically retries up to 3 times on `OptimisticLockError`
|
48
|
+
- Raises `RecordExistsError` if object already exists
|
49
|
+
- More atomic and thread-safe
|
50
|
+
|
51
|
+
## Enhanced Error Handling
|
52
|
+
|
53
|
+
### New Error Hierarchy
|
54
|
+
|
55
|
+
**What's New:**
|
56
|
+
|
57
|
+
A structured error hierarchy provides better error categorization:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
Familia::Problem # Base class
|
61
|
+
├── Familia::PersistenceError # Redis/database errors
|
62
|
+
│ ├── Familia::NonUniqueKey
|
63
|
+
│ ├── Familia::OptimisticLockError
|
64
|
+
│ ├── Familia::OperationModeError
|
65
|
+
│ ├── Familia::NoConnectionAvailable
|
66
|
+
│ ├── Familia::NotFound
|
67
|
+
│ └── Familia::NotConnected
|
68
|
+
└── Familia::HorreumError # Model-related errors
|
69
|
+
├── Familia::CreationError
|
70
|
+
├── Familia::NoIdentifier
|
71
|
+
├── Familia::FieldTypeError
|
72
|
+
├── Familia::AutoloadError
|
73
|
+
├── Familia::SerializerError
|
74
|
+
├── Familia::UnknownFieldError
|
75
|
+
└── Familia::NotDistinguishableError
|
76
|
+
```
|
77
|
+
|
78
|
+
**Migration:**
|
79
|
+
|
80
|
+
Update exception handling to use specific error classes:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# Before
|
84
|
+
rescue Familia::Problem => e
|
85
|
+
# Handle all errors
|
86
|
+
|
87
|
+
# After - More granular handling
|
88
|
+
rescue Familia::CreationError => e
|
89
|
+
# Handle creation failures specifically
|
90
|
+
rescue Familia::OptimisticLockError => e
|
91
|
+
# Handle concurrent modification
|
92
|
+
rescue Familia::PersistenceError => e
|
93
|
+
# Handle database-related errors
|
94
|
+
```
|
95
|
+
|
96
|
+
### New Exception Types
|
97
|
+
|
98
|
+
- **`CreationError`**: Raised when object creation fails (replaces generic errors)
|
99
|
+
- **`OptimisticLockError`**: Raised when WATCH fails due to concurrent modification
|
100
|
+
- **`UnknownFieldError`**: Raised when referencing non-existent fields
|
101
|
+
|
102
|
+
## Database Operation Improvements
|
103
|
+
|
104
|
+
### Atomic Save Operations
|
105
|
+
|
106
|
+
**What Changed:**
|
107
|
+
|
108
|
+
The `save` method now uses a single Redis transaction for complete atomicity:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
# All operations now happen atomically:
|
112
|
+
# 1. Save all fields (HMSET)
|
113
|
+
# 2. Set expiration (EXPIRE)
|
114
|
+
# 3. Update indexes
|
115
|
+
# 4. Add to instances collection
|
116
|
+
```
|
117
|
+
|
118
|
+
**Benefits:**
|
119
|
+
- Eliminates race conditions during save operations
|
120
|
+
- Ensures data consistency across related operations
|
121
|
+
- Better performance with fewer round trips
|
122
|
+
|
123
|
+
### Enhanced Timestamp Precision
|
124
|
+
|
125
|
+
**What Changed:**
|
126
|
+
|
127
|
+
Created/updated timestamps now use float values instead of integers for higher precision:
|
128
|
+
|
129
|
+
**Before:**
|
130
|
+
```ruby
|
131
|
+
user.created # => 1697234567 (integer seconds)
|
132
|
+
```
|
133
|
+
|
134
|
+
**After:**
|
135
|
+
```ruby
|
136
|
+
user.created # => 1697234567.123 (float with milliseconds)
|
137
|
+
```
|
138
|
+
|
139
|
+
**Migration:**
|
140
|
+
|
141
|
+
No code changes needed. Existing integer timestamps continue to work.
|
142
|
+
|
143
|
+
## Redis Command Enhancements
|
144
|
+
|
145
|
+
### New Commands Available
|
146
|
+
|
147
|
+
Added support for optimistic locking commands:
|
148
|
+
- `watch(key)` - Watch key for changes
|
149
|
+
- `unwatch()` - Remove all watches
|
150
|
+
- `discard()` - Discard queued commands
|
151
|
+
|
152
|
+
### Improved Command Logging
|
153
|
+
|
154
|
+
Database command logging now includes:
|
155
|
+
- Structured format for better readability
|
156
|
+
- Pipelined operation tracking
|
157
|
+
- Transaction boundary markers
|
158
|
+
- Command timing information
|
159
|
+
|
160
|
+
## Terminology Updates
|
161
|
+
|
162
|
+
**What Changed:**
|
163
|
+
|
164
|
+
Standardized on "pipelined" terminology throughout (previously mixed "pipeline"/"pipelined").
|
165
|
+
|
166
|
+
**Files Affected:**
|
167
|
+
- Method names now consistently use "pipelined"
|
168
|
+
- Documentation updated to match Redis terminology
|
169
|
+
- Log messages standardized
|
170
|
+
|
171
|
+
**Migration:**
|
172
|
+
|
173
|
+
No code changes needed - this was an internal consistency improvement.
|
174
|
+
|
175
|
+
## Recommended Actions
|
176
|
+
|
177
|
+
1. **Update method calls:**
|
178
|
+
```ruby
|
179
|
+
# Replace all instances
|
180
|
+
Model.create(...) → Model.create!(...)
|
181
|
+
obj.save_if_not_exists → obj.save_if_not_exists!
|
182
|
+
```
|
183
|
+
|
184
|
+
2. **Review error handling:**
|
185
|
+
```ruby
|
186
|
+
# Consider more specific error handling
|
187
|
+
rescue Familia::CreationError
|
188
|
+
rescue Familia::OptimisticLockError
|
189
|
+
```
|
190
|
+
|
191
|
+
3. **Test concurrent operations:**
|
192
|
+
- The new optimistic locking provides better concurrency handling
|
193
|
+
- Verify your application handles `OptimisticLockError` appropriately
|
194
|
+
|
195
|
+
4. **Review logging:**
|
196
|
+
- Enhanced database command logging may affect log volume
|
197
|
+
- Adjust log levels if needed for production environments
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# Qodo Merge Compliance Configuration
|
2
|
+
|
3
|
+
This document describes the Qodo Merge (formerly PR-Agent) configuration for the Familia project.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Qodo Merge provides automated PR analysis, code reviews, and compliance checks. Our configuration enables two key compliance features:
|
8
|
+
|
9
|
+
1. **Codebase Duplication Compliance** - Uses RAG (Retrieval-Augmented Generation) to check for duplicate code across related repositories
|
10
|
+
2. **Custom Compliance** - Project-specific rules tailored to Familia's development practices
|
11
|
+
|
12
|
+
## Configuration Files
|
13
|
+
|
14
|
+
### pr_agent.toml
|
15
|
+
|
16
|
+
The main Qodo Merge configuration file located in the repository root. It includes:
|
17
|
+
|
18
|
+
- **Response Language**: Set to English for consistency
|
19
|
+
- **RAG Context Enrichment**: Enabled with related repositories (`delano/familia`, `delano/tryouts`, `delano/otto`)
|
20
|
+
- **Custom Compliance Path**: References our custom compliance checklist
|
21
|
+
- **Ignore Rules**: Excludes generated files and build artifacts from analysis
|
22
|
+
|
23
|
+
### pr_compliance_checklist.yaml
|
24
|
+
|
25
|
+
Custom compliance rules specific to Familia development:
|
26
|
+
|
27
|
+
#### ErrorHandling
|
28
|
+
All external API calls and database operations must have proper error handling with try-catch blocks or appropriate error handling mechanisms.
|
29
|
+
|
30
|
+
#### TestCoverage
|
31
|
+
New features must include tests using the Tryouts framework. Test files should be in the `try/` directory following the `*_try.rb` or `*.try.rb` naming convention.
|
32
|
+
|
33
|
+
#### ChangelogFragment
|
34
|
+
User-facing changes must include a changelog fragment in the `changelog.d/` directory following RST format, or provide explicit justification for omission.
|
35
|
+
|
36
|
+
#### DocumentationUpdates
|
37
|
+
API changes must be reflected in documentation, including YARD comments for new public methods or updates to the `docs/` directory.
|
38
|
+
|
39
|
+
#### BackwardCompatibility
|
40
|
+
Changes must maintain backward compatibility or document breaking changes in migration guides with deprecation warnings.
|
41
|
+
|
42
|
+
#### ThreadSafety
|
43
|
+
Code handling shared state must be thread-safe with proper synchronization or clear documentation of thread-safety assumptions.
|
44
|
+
|
45
|
+
#### DatabaseKeyNaming
|
46
|
+
Database key generation must follow Familia conventions:
|
47
|
+
- Use the configured `delim` separator (default `:`)
|
48
|
+
- Avoid reserved keywords: `ttl`, `db`, `valkey`, `redis`
|
49
|
+
- Handle empty identifiers to prevent stack overflow
|
50
|
+
|
51
|
+
## Interactive Commands
|
52
|
+
|
53
|
+
Team members can trigger on-demand Qodo Merge analysis in PR comments:
|
54
|
+
|
55
|
+
- `/analyze --review` - Run code review
|
56
|
+
- `/analyze --test` - Generate test suggestions
|
57
|
+
- `/improve` - Get improvement suggestions
|
58
|
+
- `/ask` - Ask questions about the PR
|
59
|
+
|
60
|
+
## Compliance Status
|
61
|
+
|
62
|
+
In PR comments from `@qodo-merge-pro`, you'll see:
|
63
|
+
|
64
|
+
- 🟢 Green circle - Compliance check passed
|
65
|
+
- 🔴 Red circle - Compliance check failed with details
|
66
|
+
- ⚪ White circle - Compliance check not configured (should not appear with proper configuration)
|
67
|
+
|
68
|
+
## References
|
69
|
+
|
70
|
+
- [Qodo Merge Configuration Options](https://qodo-merge-docs.qodo.ai/usage-guide/configuration_options/)
|
71
|
+
- [RAG Context Enrichment Guide](https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/)
|
72
|
+
- [Compliance Guide](https://qodo-merge-docs.qodo.ai/tools/compliance/)
|
73
|
+
- [Best Practices](https://docs.qodo.ai/qodo-documentation/qodo-merge/features/best-practices)
|
74
|
+
|
75
|
+
## Maintenance
|
76
|
+
|
77
|
+
### Updating Compliance Rules
|
78
|
+
|
79
|
+
To add or modify compliance rules:
|
80
|
+
|
81
|
+
1. Edit `pr_compliance_checklist.yaml`
|
82
|
+
2. Ensure YAML syntax is valid: `ruby -r yaml -e "YAML.load_file('pr_compliance_checklist.yaml')"`
|
83
|
+
3. Commit changes - they take effect immediately on new PRs
|
84
|
+
|
85
|
+
### Updating Ignore Rules
|
86
|
+
|
87
|
+
To exclude additional files from analysis:
|
88
|
+
|
89
|
+
1. Edit the `[ignore]` section in `pr_agent.toml`
|
90
|
+
2. Use glob patterns to match file paths
|
91
|
+
3. Test with new PRs to verify exclusions work as expected
|
92
|
+
|
93
|
+
## Future Improvements (Optional)
|
94
|
+
|
95
|
+
- **Centralized Configuration**: Create a `pr-agent-settings` repository with `metadata.yaml` to share configuration across all repos
|
96
|
+
- **Wiki Configuration**: Enable repo wiki and create `.pr_agent.toml` page (wiki config takes precedence over local files)
|