familia 2.0.0.pre15 → 2.0.0.pre17
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/.github/workflows/ci.yml +2 -2
- data/.github/workflows/code-quality.yml +138 -0
- data/.github/workflows/code-smells.yml +85 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +3 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +54 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +86 -4
- data/CLAUDE.md +39 -1
- data/Gemfile +6 -5
- data/Gemfile.lock +99 -23
- data/LICENSE.txt +1 -1
- data/README.md +285 -85
- data/changelog.d/README.md +2 -2
- data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
- data/docs/archive/FAMILIA_TECHNICAL.md +42 -42
- data/docs/archive/FAMILIA_UPDATE.md +3 -3
- data/docs/archive/README.md +3 -2
- data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
- data/docs/conf.py +29 -0
- data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
- data/docs/guides/feature-encrypted-fields.md +785 -0
- data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
- data/docs/guides/feature-external-identifiers.md +637 -0
- data/docs/guides/feature-object-identifiers.md +435 -0
- data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
- data/docs/guides/feature-relationships-methods.md +684 -0
- data/docs/guides/feature-relationships.md +200 -0
- data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
- data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
- data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
- data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
- data/docs/guides/index.md +176 -0
- data/docs/guides/{Security-Model.md → security-model.md} +1 -1
- data/docs/migrating/v2.0.0-pre.md +1 -1
- data/docs/migrating/v2.0.0-pre11.md +2 -2
- data/docs/migrating/v2.0.0-pre12.md +2 -2
- data/docs/migrating/v2.0.0-pre5.md +33 -12
- data/docs/migrating/v2.0.0-pre6.md +2 -2
- data/docs/migrating/v2.0.0-pre7.md +8 -8
- data/docs/overview.md +624 -20
- data/docs/reference/api-technical.md +1365 -0
- data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
- data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
- data/examples/autoloader/mega_customer.rb +3 -1
- data/examples/encrypted_fields.rb +378 -0
- data/examples/json_usage_patterns.rb +144 -0
- data/examples/relationships.rb +13 -13
- data/examples/safe_dump.rb +7 -7
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +51 -10
- data/lib/familia/connection/handlers.rb +223 -0
- data/lib/familia/connection/individual_command_proxy.rb +64 -0
- data/lib/familia/connection/middleware.rb +75 -0
- data/lib/familia/connection/operation_core.rb +93 -0
- data/lib/familia/connection/operations.rb +277 -0
- data/lib/familia/connection/pipeline_core.rb +87 -0
- data/lib/familia/connection/transaction_core.rb +100 -0
- data/lib/familia/connection.rb +60 -186
- data/lib/familia/data_type/class_methods.rb +63 -0
- data/lib/familia/data_type/commands.rb +53 -51
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +15 -11
- data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
- data/lib/familia/data_type/types/lock.rb +3 -2
- data/lib/familia/data_type/types/sorted_set.rb +128 -14
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -9
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +12 -171
- data/lib/familia/distinguisher.rb +85 -0
- data/lib/familia/encryption/encrypted_data.rb +15 -24
- data/lib/familia/encryption/manager.rb +6 -4
- data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
- data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
- data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
- data/lib/familia/encryption/request_cache.rb +7 -7
- data/lib/familia/encryption.rb +2 -3
- data/lib/familia/errors.rb +9 -3
- data/lib/familia/features/autoloader.rb +30 -12
- data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
- data/lib/familia/features/encrypted_fields.rb +71 -66
- data/lib/familia/features/expiration/extensions.rb +1 -1
- data/lib/familia/features/expiration.rb +31 -26
- data/lib/familia/features/external_identifier.rb +57 -19
- data/lib/familia/features/object_identifier.rb +134 -25
- data/lib/familia/features/quantization.rb +16 -21
- data/lib/familia/features/relationships/README.md +97 -0
- data/lib/familia/features/relationships/collection_operations.rb +104 -0
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +306 -0
- data/lib/familia/features/relationships/indexing.rb +182 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +164 -0
- data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
- data/lib/familia/features/relationships/participation.rb +656 -0
- data/lib/familia/features/relationships/participation_relationship.rb +31 -0
- data/lib/familia/features/relationships/score_encoding.rb +20 -20
- data/lib/familia/features/relationships.rb +65 -266
- data/lib/familia/features/safe_dump.rb +127 -130
- data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
- data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
- data/lib/familia/features/transient_fields.rb +10 -7
- data/lib/familia/features.rb +10 -14
- data/lib/familia/field_type.rb +6 -4
- data/lib/familia/horreum/connection.rb +297 -0
- data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +27 -17
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +139 -74
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +73 -27
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +108 -185
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +104 -23
- data/lib/familia/horreum/serialization.rb +172 -0
- data/lib/familia/horreum/{shared/settings.rb → settings.rb} +2 -1
- data/lib/familia/horreum/{core/utils.rb → utils.rb} +2 -1
- data/lib/familia/horreum.rb +222 -119
- data/lib/familia/json_serializer.rb +0 -1
- data/lib/familia/logging.rb +11 -114
- data/lib/familia/refinements/dear_json.rb +122 -0
- data/lib/familia/refinements/logger_trace.rb +20 -17
- data/lib/familia/refinements/stylize_words.rb +65 -0
- data/lib/familia/refinements/time_literals.rb +60 -52
- data/lib/familia/refinements.rb +2 -1
- data/lib/familia/secure_identifier.rb +60 -28
- data/lib/familia/settings.rb +83 -7
- data/lib/familia/utils.rb +5 -87
- data/lib/familia/verifiable_identifier.rb +4 -4
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +72 -14
- data/lib/middleware/database_middleware.rb +56 -14
- data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
- data/try/configuration/scenarios_try.rb +2 -2
- data/try/connection/fiber_context_preservation_try.rb +250 -0
- data/try/connection/handler_constraints_try.rb +59 -0
- data/try/connection/operation_mode_guards_try.rb +208 -0
- data/try/connection/pipeline_fallback_integration_try.rb +128 -0
- data/try/connection/responsibility_chain_tracking_try.rb +72 -0
- data/try/connection/transaction_fallback_integration_try.rb +288 -0
- data/try/connection/transaction_mode_permissive_try.rb +153 -0
- data/try/connection/transaction_mode_strict_try.rb +98 -0
- data/try/connection/transaction_mode_warn_try.rb +131 -0
- data/try/connection/transaction_modes_try.rb +249 -0
- data/try/core/autoloader_try.rb +120 -2
- data/try/core/connection_try.rb +10 -10
- data/try/core/conventional_inheritance_try.rb +130 -0
- data/try/core/create_method_try.rb +15 -23
- data/try/core/database_consistency_try.rb +11 -10
- data/try/core/errors_try.rb +11 -14
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- data/try/core/familia_try.rb +1 -1
- data/try/core/isolated_dbclient_try.rb +165 -0
- data/try/core/middleware_try.rb +16 -16
- data/try/core/persistence_operations_try.rb +4 -4
- data/try/core/pools_try.rb +42 -26
- data/try/core/secure_identifier_try.rb +28 -24
- data/try/core/time_utils_try.rb +10 -10
- data/try/core/tools_try.rb +3 -3
- data/try/core/utils_try.rb +2 -2
- data/try/data_types/boolean_try.rb +4 -4
- data/try/data_types/datatype_base_try.rb +0 -2
- data/try/data_types/list_try.rb +10 -10
- data/try/data_types/sorted_set_try.rb +5 -5
- data/try/data_types/sorted_set_zadd_options_try.rb +625 -0
- data/try/data_types/string_try.rb +12 -12
- data/try/data_types/unsortedset_try.rb +33 -0
- data/try/debugging/cache_behavior_tracer.rb +7 -7
- data/try/debugging/debug_aad_process.rb +1 -1
- data/try/debugging/debug_concealed_internal.rb +1 -1
- data/try/debugging/debug_cross_context.rb +1 -1
- data/try/debugging/debug_fresh_cross_context.rb +1 -1
- data/try/debugging/encryption_method_tracer.rb +10 -10
- data/try/edge_cases/hash_symbolization_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/encryption/config_persistence_try.rb +2 -2
- data/try/encryption/encryption_core_try.rb +19 -19
- data/try/encryption/instance_variable_scope_try.rb +1 -1
- data/try/encryption/module_loading_try.rb +2 -2
- data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
- data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/encryption/secure_memory_handling_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
- data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
- data/try/features/encrypted_fields/fresh_key_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 +7 -7
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +3 -3
- data/try/features/field_groups_try.rb +244 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
- data/try/features/object_identifier/object_identifier_try.rb +10 -0
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
- data/try/features/relationships/indexing_try.rb +443 -0
- data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
- data/try/features/relationships/participation_commands_verification_try.rb +105 -0
- data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
- data/try/features/relationships/participation_reverse_index_try.rb +196 -0
- data/try/features/relationships/relationships_api_changes_try.rb +72 -71
- data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
- data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
- data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
- data/try/features/relationships/relationships_performance_try.rb +20 -20
- data/try/features/relationships/relationships_try.rb +27 -38
- data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
- data/try/features/transient_fields/refresh_reset_try.rb +3 -1
- data/try/features/transient_fields/simple_refresh_test.rb +1 -1
- data/try/helpers/test_cleanup.rb +86 -0
- data/try/helpers/test_helpers.rb +6 -7
- data/try/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +3 -1
- data/try/horreum/defensive_initialization_try.rb +86 -0
- data/try/horreum/destroy_related_fields_cleanup_try.rb +332 -0
- data/try/horreum/initialization_try.rb +11 -7
- data/try/horreum/relations_try.rb +21 -13
- data/try/horreum/serialization_try.rb +12 -11
- data/try/horreum/settings_try.rb +2 -0
- data/try/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +2 -2
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +13 -15
- data/try/models/datatype_base_try.rb +3 -3
- data/try/models/familia_object_try.rb +9 -8
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
- data/try/prototypes/atomic_saves_v4.rb +1 -1
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
- data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
- data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
- data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
- data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
- data/try/prototypes/pooling/pool_siege.rb +11 -11
- data/try/prototypes/pooling/run_stress_tests.rb +7 -7
- data/try/refinements/dear_json_array_methods_try.rb +53 -0
- data/try/refinements/dear_json_hash_methods_try.rb +54 -0
- data/try/refinements/logger_trace_methods_try.rb +44 -0
- data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
- data/try/refinements/time_literals_string_methods_try.rb +80 -0
- data/try/valkey.conf +26 -0
- metadata +92 -52
- data/.rubocop_todo.yml +0 -208
- data/docs/connection_pooling.md +0 -192
- data/docs/guides/Connection-Pooling-Guide.md +0 -437
- data/docs/guides/Encrypted-Fields-Overview.md +0 -101
- data/docs/guides/Feature-System-Autoloading.md +0 -198
- data/docs/guides/Home.md +0 -116
- data/docs/guides/Relationships-Guide.md +0 -737
- data/docs/guides/relationships-methods.md +0 -266
- data/docs/reference/auditing_database_commands.rb +0 -228
- data/examples/permissions.rb +0 -240
- data/lib/familia/features/relationships/cascading.rb +0 -437
- data/lib/familia/features/relationships/membership.rb +0 -497
- data/lib/familia/features/relationships/permission_management.rb +0 -264
- data/lib/familia/features/relationships/querying.rb +0 -615
- data/lib/familia/features/relationships/redis_operations.rb +0 -274
- data/lib/familia/features/relationships/tracking.rb +0 -418
- data/lib/familia/horreum/core/connection.rb +0 -73
- data/lib/familia/horreum/core.rb +0 -21
- data/lib/familia/refinements/snake_case.rb +0 -40
- data/lib/familia/validation/command_recorder.rb +0 -336
- data/lib/familia/validation/expectations.rb +0 -519
- data/lib/familia/validation/validation_helpers.rb +0 -443
- data/lib/familia/validation/validator.rb +0 -412
- data/lib/familia/validation.rb +0 -140
- data/try/data_types/set_try.rb +0 -33
- data/try/features/relationships/categorical_permissions_try.rb +0 -515
- data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
- data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
- data/try/validation/atomic_operations_try.rb.disabled +0 -320
- data/try/validation/command_validation_try.rb.disabled +0 -207
- data/try/validation/performance_validation_try.rb.disabled +0 -324
- data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -0,0 +1,130 @@
|
|
1
|
+
require_relative '../helpers/test_helpers'
|
2
|
+
|
3
|
+
# Define test classes in global namespace
|
4
|
+
class ::TestVehicle < Familia::Horreum
|
5
|
+
identifier_field :vin
|
6
|
+
field :vin
|
7
|
+
field :make
|
8
|
+
field :model
|
9
|
+
field :year
|
10
|
+
feature :expiration
|
11
|
+
list :maintenance_log
|
12
|
+
set :tags
|
13
|
+
end
|
14
|
+
|
15
|
+
class ::TestCar < ::TestVehicle
|
16
|
+
field :doors
|
17
|
+
field :fuel_type
|
18
|
+
end
|
19
|
+
|
20
|
+
class ::TestElectricCar < ::TestCar
|
21
|
+
field :battery_capacity
|
22
|
+
field :range_miles
|
23
|
+
end
|
24
|
+
|
25
|
+
class ::TestMotorcycle < ::TestVehicle
|
26
|
+
field :engine_cc
|
27
|
+
field :has_sidecar
|
28
|
+
end
|
29
|
+
|
30
|
+
class ::TestBaseModel < Familia::Horreum
|
31
|
+
end
|
32
|
+
|
33
|
+
class ::TestConcreteModel < ::TestBaseModel
|
34
|
+
identifier_field :id
|
35
|
+
field :name
|
36
|
+
end
|
37
|
+
|
38
|
+
## Creates a parent class with various configurations
|
39
|
+
@vehicle = TestVehicle.new(vin: 'ABC123', make: 'Toyota', model: 'Camry', year: 2020)
|
40
|
+
|
41
|
+
## Parent class has expected configuration
|
42
|
+
TestVehicle.identifier_field
|
43
|
+
#=> :vin
|
44
|
+
|
45
|
+
## Parent class has fields defined
|
46
|
+
TestVehicle.fields
|
47
|
+
#=> [:vin, :make, :model, :year]
|
48
|
+
|
49
|
+
## Parent class has features enabled
|
50
|
+
TestVehicle.features_enabled
|
51
|
+
#=> [:expiration]
|
52
|
+
|
53
|
+
## Child class inherits parent identifier_field
|
54
|
+
TestCar.identifier_field
|
55
|
+
#=> :vin
|
56
|
+
|
57
|
+
## Child class inherits parent fields and adds its own
|
58
|
+
TestCar.fields
|
59
|
+
#=> [:vin, :make, :model, :year, :doors, :fuel_type]
|
60
|
+
|
61
|
+
## Child class inherits parent features
|
62
|
+
TestCar.features_enabled
|
63
|
+
#=> [:expiration]
|
64
|
+
|
65
|
+
## Child instance works with inherited configuration
|
66
|
+
@car = TestCar.new(vin: 'DEF456', make: 'Honda', model: 'Civic', year: 2021, doors: 4, fuel_type: 'gasoline')
|
67
|
+
@car.identifier
|
68
|
+
#=> "DEF456"
|
69
|
+
|
70
|
+
## Child instance can access inherited fields
|
71
|
+
@car.make
|
72
|
+
#=> "Honda"
|
73
|
+
|
74
|
+
## Child instance can access new fields
|
75
|
+
@car.doors
|
76
|
+
#=> 4
|
77
|
+
|
78
|
+
## Child instance inherits DataType relationships
|
79
|
+
@car.maintenance_log.class
|
80
|
+
#=> Familia::ListKey
|
81
|
+
|
82
|
+
## Child instance can use inherited DataType relationships
|
83
|
+
@car.tags << 'reliable'
|
84
|
+
@car.tags.members
|
85
|
+
#=> ["reliable"]
|
86
|
+
|
87
|
+
## Grandchild inherits all ancestor configuration
|
88
|
+
TestElectricCar.identifier_field
|
89
|
+
#=> :vin
|
90
|
+
|
91
|
+
## Grandchild inherits all ancestor fields
|
92
|
+
TestElectricCar.fields
|
93
|
+
#=> [:vin, :make, :model, :year, :doors, :fuel_type, :battery_capacity, :range_miles]
|
94
|
+
|
95
|
+
## Grandchild inherits all ancestor features
|
96
|
+
TestElectricCar.features_enabled
|
97
|
+
#=> [:expiration]
|
98
|
+
|
99
|
+
## Grandchild instance works correctly
|
100
|
+
@electric = TestElectricCar.new(vin: 'TESLA123', make: 'Tesla', model: 'Model 3', doors: 4, battery_capacity: 75)
|
101
|
+
@electric.identifier
|
102
|
+
#=> "TESLA123"
|
103
|
+
|
104
|
+
## Parent and child classes remain independent after inheritance
|
105
|
+
TestVehicle.fields.size
|
106
|
+
#=> 4
|
107
|
+
|
108
|
+
## Child class has correct field count
|
109
|
+
TestCar.fields.size
|
110
|
+
#=> 6
|
111
|
+
|
112
|
+
## Grandchild class has correct field count
|
113
|
+
TestElectricCar.fields.size
|
114
|
+
#=> 8
|
115
|
+
|
116
|
+
## Parent field count unchanged after adding new child
|
117
|
+
TestVehicle.fields.size
|
118
|
+
#=> 4
|
119
|
+
|
120
|
+
## New child class has correct field count
|
121
|
+
TestMotorcycle.fields.size
|
122
|
+
#=> 6
|
123
|
+
|
124
|
+
## Child of empty parent inherits correctly
|
125
|
+
TestConcreteModel.identifier_field
|
126
|
+
#=> :id
|
127
|
+
|
128
|
+
## Child of empty parent has correct fields
|
129
|
+
TestConcreteModel.fields
|
130
|
+
#=> [:name]
|
@@ -16,7 +16,7 @@ end
|
|
16
16
|
# Clean up any existing test data
|
17
17
|
cleanup_keys = []
|
18
18
|
begin
|
19
|
-
existing_test_keys = Familia.dbclient.keys('
|
19
|
+
existing_test_keys = Familia.dbclient.keys('create_test_model:*')
|
20
20
|
cleanup_keys.concat(existing_test_keys)
|
21
21
|
Familia.dbclient.del(*existing_test_keys) if existing_test_keys.any?
|
22
22
|
rescue => e
|
@@ -26,16 +26,18 @@ end
|
|
26
26
|
@test_id_counter = 0
|
27
27
|
def next_test_id
|
28
28
|
@test_id_counter += 1
|
29
|
-
"create-test-#{
|
29
|
+
identifier = "create-test-#{Familia.now.to_i}-#{@test_id_counter}"
|
30
|
+
identifier
|
30
31
|
end
|
31
32
|
|
33
|
+
@first_test_id = next_test_id
|
34
|
+
|
32
35
|
# =============================================
|
33
36
|
# 1. Basic create method functionality
|
34
37
|
# =============================================
|
35
38
|
|
36
39
|
## create method successfully creates new object
|
37
|
-
@
|
38
|
-
@created_obj = CreateTestModel.create(id: @test_id, name: 'Created Object', value: 'test_value')
|
40
|
+
@created_obj = CreateTestModel.create(id: @first_test_id, name: 'Created Object', value: 'test_value')
|
39
41
|
[@created_obj.class, @created_obj.exists?, @created_obj.name]
|
40
42
|
#=> [CreateTestModel, true, 'Created Object']
|
41
43
|
|
@@ -53,27 +55,17 @@ end
|
|
53
55
|
# =============================================
|
54
56
|
|
55
57
|
## create method raises RecordExistsError for duplicate
|
56
|
-
|
57
|
-
|
58
|
-
false # Should not reach here
|
59
|
-
rescue => e
|
60
|
-
e.class
|
61
|
-
end
|
62
|
-
#=> Familia::RecordExistsError
|
58
|
+
CreateTestModel.create(id: @first_test_id, name: 'Duplicate Attempt')
|
59
|
+
#=!> Familia::RecordExistsError
|
63
60
|
|
64
61
|
## RecordExistsError includes the dbkey in the message
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
rescue Familia::RecordExistsError => e
|
69
|
-
expected_dbkey = "createtestmodel:#{@test_id}:object"
|
70
|
-
e.message.include?(expected_dbkey)
|
71
|
-
end
|
72
|
-
#=> true
|
62
|
+
CreateTestModel.create(id: @first_test_id, name: 'Another Duplicate')
|
63
|
+
#=!> Familia::RecordExistsError
|
64
|
+
#==> !!error.message.match(/create_test_model:#{@first_test_id}:object/)
|
73
65
|
|
74
66
|
## RecordExistsError message follows consistent format
|
75
67
|
begin
|
76
|
-
CreateTestModel.create(id: @
|
68
|
+
CreateTestModel.create(id: @first_test_id, name: 'Yet Another Duplicate')
|
77
69
|
false # Should not reach here
|
78
70
|
rescue Familia::RecordExistsError => e
|
79
71
|
e.message.start_with?('Key already exists:')
|
@@ -140,12 +132,12 @@ end
|
|
140
132
|
#=> true
|
141
133
|
|
142
134
|
## create failure doesn't leave partial data
|
143
|
-
before_failed_create = Familia.dbclient.keys("
|
135
|
+
before_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
|
144
136
|
begin
|
145
137
|
CreateTestModel.create(id: @concurrent_id, name: 'Should Fail')
|
146
138
|
rescue Familia::RecordExistsError
|
147
139
|
# Should not create any additional keys
|
148
|
-
after_failed_create = Familia.dbclient.keys("
|
140
|
+
after_failed_create = Familia.dbclient.keys("create_test_model:#{@concurrent_id}:*").length
|
149
141
|
after_failed_create == before_failed_create
|
150
142
|
end
|
151
143
|
#=> true
|
@@ -236,5 +228,5 @@ instance_sees_exists = @class_created.exists?
|
|
236
228
|
# =============================================
|
237
229
|
|
238
230
|
# Clean up all test data
|
239
|
-
test_keys = Familia.dbclient.keys('
|
231
|
+
test_keys = Familia.dbclient.keys('create_test_model:*')
|
240
232
|
Familia.dbclient.del(*test_keys) if test_keys.any?
|
@@ -28,14 +28,14 @@ end
|
|
28
28
|
@test_id_counter = 0
|
29
29
|
def next_test_id
|
30
30
|
@test_id_counter += 1
|
31
|
-
"consistency-#{
|
31
|
+
"consistency-#{Familia.now.to_i}-#{@test_id_counter}"
|
32
32
|
end
|
33
33
|
|
34
34
|
# =============================================
|
35
35
|
# 1. Database Consistency Verification
|
36
36
|
# =============================================
|
37
37
|
|
38
|
-
## Redis key structure follows expected pattern
|
38
|
+
## Valkey/Redis key structure follows expected pattern
|
39
39
|
@key_test = ConsistencyTestModel.new(id: next_test_id, name: 'Key Test')
|
40
40
|
@key_test.save
|
41
41
|
dbkey = @key_test.dbkey
|
@@ -139,7 +139,7 @@ end
|
|
139
139
|
@empty_hash.save
|
140
140
|
|
141
141
|
# Manually remove all fields to create an empty hash
|
142
|
-
# First add a temp field then remove it, which creates empty hash in some Redis versions
|
142
|
+
# First add a temp field then remove it, which creates empty hash in some Valkey/Redis versions
|
143
143
|
Familia.dbclient.hset(@empty_hash.dbkey, 'temp_field', 'temp_value')
|
144
144
|
Familia.dbclient.hdel(@empty_hash.dbkey, 'temp_field')
|
145
145
|
# Now remove all remaining fields to create truly empty hash
|
@@ -159,17 +159,17 @@ obj_exists_without_check = @empty_hash.exists?(check_size: false)
|
|
159
159
|
@tx_test = ConsistencyTestModel.new(id: next_test_id, name: 'Transaction Test')
|
160
160
|
@tx_test.save
|
161
161
|
|
162
|
-
# Verify transaction doesn't
|
163
|
-
|
164
|
-
|
165
|
-
|
162
|
+
# Verify transaction works and doesn't corrupt object state
|
163
|
+
exists_before_tx = @tx_test.exists?
|
164
|
+
multi_result = @tx_test.transaction do |conn|
|
165
|
+
# Update a field within transaction
|
166
166
|
conn.hset(@tx_test.dbkey, 'active', 'true')
|
167
|
-
|
167
|
+
conn.hset(@tx_test.dbkey, 'processed', 'true')
|
168
168
|
end
|
169
169
|
|
170
170
|
exists_after_tx = @tx_test.exists?
|
171
|
-
[
|
172
|
-
#=> [
|
171
|
+
[exists_before_tx, multi_result.successful?, exists_after_tx]
|
172
|
+
#=> [true, true, true]
|
173
173
|
|
174
174
|
# =============================================
|
175
175
|
# 4. Performance Consistency
|
@@ -214,6 +214,7 @@ exists_after_batch = @batch_obj.exists?
|
|
214
214
|
|
215
215
|
## Transient fields don't affect exists? behavior
|
216
216
|
class TransientConsistencyTest < Familia::Horreum
|
217
|
+
feature :transient_fields
|
217
218
|
identifier_field :id
|
218
219
|
field :id
|
219
220
|
field :name
|
data/try/core/errors_try.rb
CHANGED
@@ -26,33 +26,30 @@ rescue Familia::NonUniqueKey => e
|
|
26
26
|
end
|
27
27
|
#=> Familia::NonUniqueKey
|
28
28
|
|
29
|
-
##
|
29
|
+
## NotDistinguishableError error stores value
|
30
30
|
begin
|
31
|
-
raise Familia::
|
32
|
-
rescue Familia::
|
31
|
+
raise Familia::NotDistinguishableError.new('dangerous_value')
|
32
|
+
rescue Familia::NotDistinguishableError => e
|
33
33
|
e.value
|
34
34
|
end
|
35
35
|
#=> "dangerous_value"
|
36
36
|
|
37
|
-
##
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
e.message.include?('High risk factor')
|
42
|
-
end
|
43
|
-
#=> true
|
37
|
+
## NotDistinguishableError error has custom message
|
38
|
+
raise Familia::NotDistinguishableError, 'A customized message'
|
39
|
+
#=:> Familia::NotDistinguishableError
|
40
|
+
#=~> /A customized message/
|
44
41
|
|
45
42
|
## NotConnected error stores URI
|
46
|
-
test_uri = URI.parse('redis://localhost:
|
43
|
+
test_uri = URI.parse('redis://localhost:2525')
|
47
44
|
begin
|
48
45
|
raise Familia::NotConnected.new(test_uri)
|
49
46
|
rescue Familia::NotConnected => e
|
50
47
|
e.uri.to_s
|
51
48
|
end
|
52
|
-
#=> "redis://localhost"
|
49
|
+
#=> "redis://localhost:2525"
|
53
50
|
|
54
51
|
## NotConnected error has custom message
|
55
|
-
test_uri = URI.parse('redis://localhost:
|
52
|
+
test_uri = URI.parse('redis://localhost:2525')
|
56
53
|
begin
|
57
54
|
raise Familia::NotConnected.new(test_uri)
|
58
55
|
rescue Familia::NotConnected => e
|
@@ -105,7 +102,7 @@ Familia::RecordExistsError.superclass
|
|
105
102
|
[
|
106
103
|
Familia::NoIdentifier,
|
107
104
|
Familia::NonUniqueKey,
|
108
|
-
Familia::
|
105
|
+
Familia::NotDistinguishableError,
|
109
106
|
Familia::NotConnected,
|
110
107
|
Familia::KeyNotFoundError,
|
111
108
|
Familia::RecordExistsError
|
@@ -7,7 +7,7 @@ require_relative '../helpers/test_helpers'
|
|
7
7
|
## Has all datatype relativess
|
8
8
|
registered_types = Familia::DataType.registered_types.keys
|
9
9
|
registered_types.collect(&:to_s).sort
|
10
|
-
#=> ["counter", "hash", "hashkey", "list", "lock", "set", "sorted_set", "string", "zset"]
|
10
|
+
#=> ["counter", "hash", "hashkey", "list", "listkey", "lock", "set", "sorted_set", "string", "stringkey", "unsorted_set", "zset"]
|
11
11
|
|
12
12
|
## Familia created class methods for datatype list class
|
13
13
|
Familia::Horreum::DefinitionMethods.public_method_defined? :list?
|
@@ -36,7 +36,7 @@ Bone.list? :owners
|
|
36
36
|
## A Familia object can get a specific datatype relatives def
|
37
37
|
definition = Bone.list :owners
|
38
38
|
definition.klass
|
39
|
-
#=> Familia::
|
39
|
+
#=> Familia::ListKey
|
40
40
|
|
41
41
|
## Familia.now
|
42
42
|
parsed_time = Familia.now(Time.parse('2011-04-10 20:56:20 UTC').utc)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# try/core/familia_members_methods_try.rb
|
2
|
+
|
3
|
+
require_relative '../helpers/test_helpers'
|
4
|
+
|
5
|
+
# Tests for new methods: demodularize, familia_name, and resolve_class
|
6
|
+
|
7
|
+
# Use the testable methods from the refactored module
|
8
|
+
String.include(Familia::Refinements::StylizeWordsMethods)
|
9
|
+
|
10
|
+
## demodularize removes module namespace from simple class name
|
11
|
+
'Customer'.demodularize
|
12
|
+
#=> 'Customer'
|
13
|
+
|
14
|
+
## demodularize removes module namespace from nested class name
|
15
|
+
'V2::Customer'.demodularize
|
16
|
+
#=> 'Customer'
|
17
|
+
|
18
|
+
## demodularize handles deep nesting
|
19
|
+
'My::Deep::Nested::Module::Customer'.demodularize
|
20
|
+
#=> 'Customer'
|
21
|
+
|
22
|
+
## demodularize handles single colon edge case
|
23
|
+
'::Customer'.demodularize
|
24
|
+
#=> 'Customer'
|
25
|
+
|
26
|
+
## demodularize returns original string when no modules
|
27
|
+
'SimpleClass'.demodularize
|
28
|
+
#=> 'SimpleClass'
|
29
|
+
|
30
|
+
## familia_name returns demodularized class name for Customer
|
31
|
+
Customer.familia_name
|
32
|
+
#=> 'Customer'
|
33
|
+
|
34
|
+
## familia_name returns demodularized class name for Session
|
35
|
+
Session.familia_name
|
36
|
+
#=> 'Session'
|
37
|
+
|
38
|
+
## familia_name returns demodularized class name for Bone
|
39
|
+
Bone.familia_name
|
40
|
+
#=> 'Bone'
|
41
|
+
|
42
|
+
## resolve_class returns the same class when given a Class
|
43
|
+
Familia.resolve_class(Customer)
|
44
|
+
#=> Customer
|
45
|
+
|
46
|
+
## resolve_class finds class by string name
|
47
|
+
Familia.resolve_class('Customer')
|
48
|
+
#=> Customer
|
49
|
+
|
50
|
+
## resolve_class finds class by symbol name
|
51
|
+
Familia.resolve_class(:Customer)
|
52
|
+
#=> Customer
|
53
|
+
|
54
|
+
## resolve_class handles CamelCase string conversion
|
55
|
+
Familia.resolve_class('CustomDomain')
|
56
|
+
#=> CustomDomain
|
57
|
+
|
58
|
+
## resolve_class handles snake_case symbol conversion
|
59
|
+
Familia.resolve_class(:CustomDomain)
|
60
|
+
#=> CustomDomain
|
61
|
+
|
62
|
+
## resolve_class raises error for invalid input
|
63
|
+
begin
|
64
|
+
Familia.resolve_class(123)
|
65
|
+
rescue ArgumentError => e
|
66
|
+
e.message
|
67
|
+
end
|
68
|
+
#=> "Expected Class, String, or Symbol, got Integer"
|
69
|
+
|
70
|
+
## resolve_class returns nil for unknown class name
|
71
|
+
Familia.resolve_class('NonExistentClass')
|
72
|
+
#=> nil
|
73
|
+
|
74
|
+
## resolve_class returns nil for unknown symbol
|
75
|
+
Familia.resolve_class(:NonExistentClass)
|
76
|
+
#=> nil
|
data/try/core/familia_try.rb
CHANGED
@@ -0,0 +1,165 @@
|
|
1
|
+
# try/core/isolated_dbclient_try.rb
|
2
|
+
|
3
|
+
# Tryouts: Isolated connection functionality
|
4
|
+
#
|
5
|
+
# Tests for isolated database connections that don't interfere
|
6
|
+
# with the cached connection pool or existing model connections.
|
7
|
+
|
8
|
+
require_relative '../helpers/test_helpers'
|
9
|
+
|
10
|
+
# Clean up any existing test data in all test databases
|
11
|
+
(0..2).each do |db|
|
12
|
+
Familia.with_isolated_dbclient(db) do |client|
|
13
|
+
client.flushdb
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
## isolated_dbclient creates a new uncached connection
|
18
|
+
client1 = Familia.isolated_dbclient(0)
|
19
|
+
client2 = Familia.isolated_dbclient(0)
|
20
|
+
different_objects = client1.object_id != client2.object_id
|
21
|
+
client1.close
|
22
|
+
client2.close
|
23
|
+
different_objects
|
24
|
+
#=> true
|
25
|
+
|
26
|
+
## isolated_dbclient connects to the correct database
|
27
|
+
Familia.with_isolated_dbclient(5) do |client|
|
28
|
+
client.set("test_key", "test_value")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Verify the key was set in database 5
|
32
|
+
found_in_db5 = Familia.with_isolated_dbclient(5) do |client|
|
33
|
+
client.get("test_key") == "test_value"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Verify the key is NOT in database 0
|
37
|
+
not_found_in_db0 = Familia.with_isolated_dbclient(0) do |client|
|
38
|
+
client.get("test_key").nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
found_in_db5 && not_found_in_db0
|
42
|
+
#=> true
|
43
|
+
|
44
|
+
## isolated_dbclient doesn't affect cached connections
|
45
|
+
# Set up a cached connection
|
46
|
+
regular_client = Familia.dbclient(0)
|
47
|
+
regular_client.set("cached_key", "cached_value")
|
48
|
+
|
49
|
+
# Use isolated connection on same database
|
50
|
+
isolated_result = Familia.with_isolated_dbclient(0) do |client|
|
51
|
+
client.set("isolated_key", "isolated_value")
|
52
|
+
client.get("cached_key")
|
53
|
+
end
|
54
|
+
|
55
|
+
# Both keys should be accessible
|
56
|
+
cached_accessible = regular_client.get("cached_key") == "cached_value"
|
57
|
+
isolated_accessible = regular_client.get("isolated_key") == "isolated_value"
|
58
|
+
|
59
|
+
cached_accessible && isolated_accessible && isolated_result == "cached_value"
|
60
|
+
#=> true
|
61
|
+
|
62
|
+
## with_isolated_dbclient properly manages connection lifecycle
|
63
|
+
# Test by verifying functionality rather than relying on GC/ObjectSpace
|
64
|
+
captured_clients = []
|
65
|
+
|
66
|
+
5.times do |i|
|
67
|
+
Familia.with_isolated_dbclient(i) do |client|
|
68
|
+
captured_clients << client
|
69
|
+
client.set("temp_key_#{i}", "temp_value_#{i}")
|
70
|
+
# Verify connection works inside block
|
71
|
+
client.ping
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Verify all connections worked and created distinct objects
|
76
|
+
all_worked = captured_clients.size == 5
|
77
|
+
all_distinct = captured_clients.map(&:object_id).uniq.size == 5
|
78
|
+
keys_set = Familia.with_isolated_dbclient(0) { |c| c.exists?("temp_key_0") }
|
79
|
+
|
80
|
+
all_worked && all_distinct && keys_set
|
81
|
+
#=> true
|
82
|
+
|
83
|
+
## with_isolated_dbclient handles exceptions gracefully
|
84
|
+
exception_raised = false
|
85
|
+
database_state_correct = false
|
86
|
+
|
87
|
+
begin
|
88
|
+
Familia.with_isolated_dbclient(0) do |client|
|
89
|
+
client.set("before_error", "value")
|
90
|
+
raise "Test exception"
|
91
|
+
# This line should not be reached
|
92
|
+
client.set("after_error", "should_not_be_set")
|
93
|
+
end
|
94
|
+
rescue => e
|
95
|
+
exception_raised = (e.message == "Test exception")
|
96
|
+
end
|
97
|
+
|
98
|
+
# Verify the database state after the exception was caught
|
99
|
+
if exception_raised
|
100
|
+
database_state_correct = Familia.with_isolated_dbclient(0) do |client|
|
101
|
+
client.get("before_error") == "value" && client.get("after_error").nil?
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
exception_raised && database_state_correct
|
106
|
+
#=> true
|
107
|
+
|
108
|
+
## isolated connections don't interfere with model connections
|
109
|
+
class TestModel < Familia::Horreum
|
110
|
+
logical_database 3
|
111
|
+
identifier_field :name
|
112
|
+
field :name
|
113
|
+
end
|
114
|
+
|
115
|
+
# Create a model instance
|
116
|
+
test_model = TestModel.new(name: "test")
|
117
|
+
test_model.save
|
118
|
+
|
119
|
+
# Use isolated connection to scan a different database
|
120
|
+
scan_result = Familia.with_isolated_dbclient(5) do |client|
|
121
|
+
client.keys("*")
|
122
|
+
end
|
123
|
+
|
124
|
+
# Model should still work correctly
|
125
|
+
model_accessible = test_model.exists? && test_model.name == "test"
|
126
|
+
# Don't rely on database 5 being empty since previous tests may have written to it
|
127
|
+
# Just verify the model still works correctly
|
128
|
+
scan_result_valid = scan_result.is_a?(Array)
|
129
|
+
|
130
|
+
model_accessible && scan_result_valid
|
131
|
+
#=> true
|
132
|
+
|
133
|
+
# Clean up test model and class
|
134
|
+
test_model.delete!
|
135
|
+
Familia.unload_member(TestModel)
|
136
|
+
|
137
|
+
## isolated_dbclient with Integer argument
|
138
|
+
client = Familia.isolated_dbclient(7)
|
139
|
+
client.set("db_test", "seven")
|
140
|
+
result = client.get("db_test")
|
141
|
+
client.close
|
142
|
+
result
|
143
|
+
#=> "seven"
|
144
|
+
|
145
|
+
## isolated_dbclient with String URI argument
|
146
|
+
client = Familia.isolated_dbclient("redis://localhost:2525/8")
|
147
|
+
client.set("uri_test", "eight")
|
148
|
+
result = client.get("uri_test")
|
149
|
+
client.close
|
150
|
+
result
|
151
|
+
#=> "eight"
|
152
|
+
|
153
|
+
## isolated_dbclient with nil uses default
|
154
|
+
default_db = Familia.uri.db || 0
|
155
|
+
client = Familia.isolated_dbclient(nil)
|
156
|
+
client.set("default_test", "default_value")
|
157
|
+
|
158
|
+
# Verify it's in the expected database
|
159
|
+
verification = Familia.with_isolated_dbclient(default_db) do |verify_client|
|
160
|
+
verify_client.get("default_test")
|
161
|
+
end
|
162
|
+
|
163
|
+
client.close
|
164
|
+
verification
|
165
|
+
#=> "default_value"
|
data/try/core/middleware_try.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
# try/core/middleware_try.rb
|
2
2
|
|
3
|
-
# Test Redis middleware components
|
4
|
-
# Mock Redis client with middleware for testing
|
3
|
+
# Test Valkey/Redis middleware components
|
4
|
+
# Mock Valkey/Redis client with middleware for testing
|
5
5
|
|
6
6
|
require_relative '../helpers/test_helpers'
|
7
7
|
|
8
|
-
class
|
8
|
+
class MockDatabase
|
9
9
|
attr_reader :logged_commands
|
10
10
|
|
11
11
|
def initialize
|
@@ -19,40 +19,40 @@ class MockRedis
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def log_command(cmd, *args)
|
22
|
-
start_time =
|
22
|
+
start_time = Familia.now
|
23
23
|
result = yield
|
24
|
-
duration =
|
24
|
+
duration = Familia.now - start_time
|
25
25
|
@logged_commands << { command: cmd, args: args, duration: duration }
|
26
26
|
result
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
##
|
31
|
-
|
32
|
-
result =
|
33
|
-
[result,
|
30
|
+
## MockDatabase can log commands with timing
|
31
|
+
dbclient = MockDatabase.new
|
32
|
+
result = dbclient.get("test_key")
|
33
|
+
[result, dbclient.logged_commands.length, dbclient.logged_commands.first[:command]]
|
34
34
|
#=> ["test_value", 1, "GET"]
|
35
35
|
|
36
|
-
##
|
36
|
+
## DatabaseCommandCounter tracks command metrics (if available)
|
37
37
|
begin
|
38
|
-
counter =
|
38
|
+
counter = DatabaseCommandCounter.new
|
39
39
|
counter.increment("GET")
|
40
40
|
counter.increment("SET")
|
41
41
|
counter.increment("GET")
|
42
42
|
[counter.count("GET"), counter.count("SET"), counter.total]
|
43
43
|
rescue NameError
|
44
|
-
# Skip if
|
44
|
+
# Skip if DatabaseCommandCounter not available
|
45
45
|
[2, 1, 3]
|
46
46
|
end
|
47
47
|
#=> [2, 1, 3]
|
48
48
|
|
49
49
|
## Command counting utility works (if available)
|
50
50
|
begin
|
51
|
-
|
51
|
+
dbclient = Familia.dbclient
|
52
52
|
count = count_commands do
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
dbclient.set("test_key", "value")
|
54
|
+
dbclient.get("test_key")
|
55
|
+
dbclient.del("test_key")
|
56
56
|
end
|
57
57
|
count >= 3
|
58
58
|
rescue NameError, NoMethodError
|