familia 2.0.0.pre16 → 2.0.0.pre18
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-smellage.yml → code-smells.yml} +3 -63
- data/.gitignore +2 -0
- data/.rubocop.yml +6 -0
- data/CHANGELOG.rst +82 -0
- data/CLAUDE.md +47 -2
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/bin/irb +1 -1
- data/docs/archive/FAMILIA_TECHNICAL.md +1 -1
- data/docs/guides/core-field-system.md +48 -26
- data/docs/migrating/v2.0.0-pre18.md +58 -0
- data/docs/overview.md +2 -2
- data/docs/qodo-merge-compliance.md +96 -0
- data/docs/reference/api-technical.md +1 -1
- data/examples/encrypted_fields.rb +1 -1
- data/examples/safe_dump.rb +1 -1
- data/lib/familia/base.rb +6 -6
- data/lib/familia/connection/middleware.rb +58 -4
- data/lib/familia/connection.rb +1 -1
- data/lib/familia/data_type/class_methods.rb +63 -0
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/{commands.rb → database_commands.rb} +2 -2
- data/lib/familia/data_type/serialization.rb +5 -5
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/hashkey.rb +2 -1
- data/lib/familia/data_type/types/sorted_set.rb +113 -10
- data/lib/familia/data_type/types/stringkey.rb +0 -4
- data/lib/familia/data_type.rb +8 -195
- data/lib/familia/encryption/encrypted_data.rb +12 -2
- data/lib/familia/encryption/manager.rb +11 -4
- data/lib/familia/features/autoloader.rb +3 -1
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
- data/lib/familia/features/encrypted_fields.rb +5 -2
- data/lib/familia/features/external_identifier.rb +49 -8
- data/lib/familia/features/object_identifier.rb +84 -12
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +9 -9
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +45 -26
- data/lib/familia/features/relationships/indexing.rb +7 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +6 -2
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/features/transient_fields.rb +7 -2
- data/lib/familia/features.rb +6 -1
- data/lib/familia/field_type.rb +0 -18
- data/lib/familia/horreum/{core/connection.rb → connection.rb} +21 -0
- data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +1 -1
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +102 -56
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +18 -15
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +73 -170
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +22 -2
- data/lib/familia/horreum/serialization.rb +190 -0
- data/lib/familia/horreum.rb +39 -14
- data/lib/familia/identifier_extractor.rb +60 -0
- data/lib/familia/logging.rb +271 -112
- data/lib/familia/refinements.rb +0 -1
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -2
- data/lib/middleware/{database_middleware.rb → database_logger.rb} +47 -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 +1 -1
- 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 +1 -1
- 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/field_groups_try.rb +244 -0
- 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 +16 -1
- 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 +1 -1
- 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 +3 -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 +1 -1
- data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
- data/try/{core → integration/connection}/isolated_dbclient_try.rb +3 -3
- data/try/integration/connection/middleware_reconnect_try.rb +87 -0
- data/try/{connection → integration/connection}/operation_mode_guards_try.rb +1 -1
- data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +1 -1
- 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 +1 -1
- data/try/integration/cross_component_try.rb +1 -1
- data/try/{core → integration}/database_consistency_try.rb +12 -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 +1 -1
- data/try/{models → integration/models}/customer_try.rb +6 -6
- data/try/{models → integration/models}/datatype_base_try.rb +1 -1
- data/try/{models → integration/models}/familia_object_try.rb +1 -1
- data/try/{core → integration}/persistence_operations_try.rb +1 -1
- data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
- data/try/{configuration → integration}/scenarios_try.rb +2 -2
- 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 +15 -7
- data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +1 -1
- 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 +5 -5
- data/try/{core → unit/core}/errors_try.rb +4 -4
- 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 +2 -2
- 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 +3 -3
- data/try/{core → unit/core}/utils_try.rb +17 -14
- data/try/{data_types → unit/data_types}/boolean_try.rb +1 -1
- 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/unit/data_types/sorted_set_zadd_options_try.rb +625 -0
- data/try/{data_types → unit/data_types}/string_try.rb +1 -1
- data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
- data/try/unit/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/{horreum → unit/horreum}/base_try.rb +3 -3
- data/try/{horreum → unit/horreum}/class_methods_try.rb +1 -1
- data/try/{horreum → unit/horreum}/commands_try.rb +3 -1
- data/try/unit/horreum/defensive_initialization_try.rb +86 -0
- data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +3 -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 +2 -2
- data/try/unit/horreum/json_type_preservation_try.rb +248 -0
- data/try/{horreum → unit/horreum}/relations_try.rb +1 -1
- data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
- data/try/{horreum → unit/horreum}/serialization_try.rb +4 -4
- data/try/{horreum → unit/horreum}/settings_try.rb +3 -1
- 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
- data/try/valkey.conf +26 -0
- metadata +149 -132
- data/lib/familia/distinguisher.rb +0 -85
- data/lib/familia/horreum/core.rb +0 -21
- data/lib/familia/refinements/logger_trace.rb +0 -60
- data/try/refinements/logger_trace_methods_try.rb +0 -44
- /data/lib/familia/horreum/{shared/settings.rb → settings.rb} +0 -0
- /data/lib/familia/horreum/{core/utils.rb → utils.rb} +0 -0
- /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_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
@@ -0,0 +1,248 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../support/helpers/test_helpers'
|
4
|
+
|
5
|
+
Familia.debug = false
|
6
|
+
|
7
|
+
# Setup: Define test model with various field types
|
8
|
+
class JsonTestModel < Familia::Horreum
|
9
|
+
identifier_field :model_id
|
10
|
+
field :model_id
|
11
|
+
field :name # String
|
12
|
+
field :age # Integer
|
13
|
+
field :active # Boolean
|
14
|
+
field :score # Float
|
15
|
+
field :metadata # Hash
|
16
|
+
field :tags # Array
|
17
|
+
field :nested_data # Hash with nested structures
|
18
|
+
field :badge_number # String that looks numeric
|
19
|
+
field :status # String that looks boolean
|
20
|
+
field :placeholder # String that looks like null
|
21
|
+
end
|
22
|
+
|
23
|
+
@test_id = "json_test_#{Familia.now.to_i}"
|
24
|
+
|
25
|
+
## String fields preserve String type
|
26
|
+
@model = JsonTestModel.new(model_id: @test_id, name: "Test User")
|
27
|
+
@model.save
|
28
|
+
@loaded = JsonTestModel.find(@test_id)
|
29
|
+
@loaded.name
|
30
|
+
#=> "Test User"
|
31
|
+
|
32
|
+
## String field returns String class
|
33
|
+
@loaded.name.class
|
34
|
+
#=> String
|
35
|
+
|
36
|
+
## Integer fields preserve Integer type (not String)
|
37
|
+
@model = JsonTestModel.new(model_id: @test_id, age: 35)
|
38
|
+
@model.save
|
39
|
+
@loaded = JsonTestModel.find(@test_id)
|
40
|
+
@loaded.age
|
41
|
+
#=> 35
|
42
|
+
|
43
|
+
## Integer field returns Integer class (not String)
|
44
|
+
@loaded.age.class
|
45
|
+
#=> Integer
|
46
|
+
|
47
|
+
## Boolean true preserves TrueClass type (not String "true")
|
48
|
+
@model = JsonTestModel.new(model_id: @test_id, active: true)
|
49
|
+
@model.save
|
50
|
+
@loaded = JsonTestModel.find(@test_id)
|
51
|
+
@loaded.active
|
52
|
+
#=> true
|
53
|
+
|
54
|
+
## Boolean true returns TrueClass
|
55
|
+
@loaded.active.class
|
56
|
+
#=> TrueClass
|
57
|
+
|
58
|
+
## Boolean false preserves FalseClass type (not String "false")
|
59
|
+
@model = JsonTestModel.new(model_id: @test_id, active: false)
|
60
|
+
@model.save
|
61
|
+
@loaded = JsonTestModel.find(@test_id)
|
62
|
+
@loaded.active
|
63
|
+
#=> false
|
64
|
+
|
65
|
+
## Boolean false returns FalseClass
|
66
|
+
@loaded.active.class
|
67
|
+
#=> FalseClass
|
68
|
+
|
69
|
+
## Float fields preserve Float type
|
70
|
+
@model = JsonTestModel.new(model_id: @test_id, score: 98.6)
|
71
|
+
@model.save
|
72
|
+
@loaded = JsonTestModel.find(@test_id)
|
73
|
+
@loaded.score
|
74
|
+
#=> 98.6
|
75
|
+
|
76
|
+
## Float field returns Float class
|
77
|
+
@loaded.score.class
|
78
|
+
#=> Float
|
79
|
+
|
80
|
+
## Hash fields preserve Hash structure
|
81
|
+
@model = JsonTestModel.new(model_id: @test_id, metadata: { "key" => "value", "count" => 42 })
|
82
|
+
@model.save
|
83
|
+
@loaded = JsonTestModel.find(@test_id)
|
84
|
+
@loaded.metadata
|
85
|
+
#=> {"key"=>"value", "count"=>42}
|
86
|
+
|
87
|
+
## Hash field returns Hash class
|
88
|
+
@loaded.metadata.class
|
89
|
+
#=> Hash
|
90
|
+
|
91
|
+
## Array fields preserve Array structure and element types
|
92
|
+
@model = JsonTestModel.new(model_id: @test_id, tags: ["ruby", "redis", 123, true])
|
93
|
+
@model.save
|
94
|
+
@loaded = JsonTestModel.find(@test_id)
|
95
|
+
@loaded.tags
|
96
|
+
#=> ["ruby", "redis", 123, true]
|
97
|
+
|
98
|
+
## Array field returns Array class
|
99
|
+
@loaded.tags.class
|
100
|
+
#=> Array
|
101
|
+
|
102
|
+
## Array preserves Integer element type
|
103
|
+
@loaded.tags[2].class
|
104
|
+
#=> Integer
|
105
|
+
|
106
|
+
## Array preserves Boolean element type
|
107
|
+
@loaded.tags[3].class
|
108
|
+
#=> TrueClass
|
109
|
+
|
110
|
+
## Nested Hash structures preserve types at all levels
|
111
|
+
@nested = {
|
112
|
+
"user" => {
|
113
|
+
"name" => "John",
|
114
|
+
"age" => 30,
|
115
|
+
"active" => true,
|
116
|
+
"roles" => ["admin", "user"]
|
117
|
+
},
|
118
|
+
"count" => 5
|
119
|
+
}
|
120
|
+
@model = JsonTestModel.new(model_id: @test_id, nested_data: @nested)
|
121
|
+
@model.save
|
122
|
+
@loaded = JsonTestModel.find(@test_id)
|
123
|
+
@loaded.nested_data["user"]["age"].class
|
124
|
+
#=> Integer
|
125
|
+
|
126
|
+
## Nested Hash Boolean preserved
|
127
|
+
@loaded.nested_data["user"]["active"].class
|
128
|
+
#=> TrueClass
|
129
|
+
|
130
|
+
## Nested Hash top-level Integer preserved
|
131
|
+
@loaded.nested_data["count"].class
|
132
|
+
#=> Integer
|
133
|
+
## Round-trip consistency: save → load → save → load maintains types
|
134
|
+
@model = JsonTestModel.new(
|
135
|
+
model_id: @test_id,
|
136
|
+
name: "Round Trip",
|
137
|
+
age: 42,
|
138
|
+
active: true,
|
139
|
+
score: 99.9,
|
140
|
+
tags: [1, 2, 3]
|
141
|
+
)
|
142
|
+
@model.save
|
143
|
+
@first_load = JsonTestModel.find(@test_id)
|
144
|
+
@first_load.save # Re-save without modification
|
145
|
+
@second_load = JsonTestModel.find(@test_id)
|
146
|
+
[@second_load.age.class, @second_load.active.class, @second_load.tags[0].class]
|
147
|
+
#=> [Integer, TrueClass, Integer]
|
148
|
+
|
149
|
+
## batch_update preserves types
|
150
|
+
@model = JsonTestModel.new(model_id: @test_id)
|
151
|
+
@model.batch_update(name: "Batch", age: 50, active: false, tags: ["one", 2])
|
152
|
+
@loaded = JsonTestModel.find(@test_id)
|
153
|
+
[@loaded.name, @loaded.age, @loaded.active, @loaded.tags[1]]
|
154
|
+
#=> ["Batch", 50, false, 2]
|
155
|
+
|
156
|
+
## batch_update types verification
|
157
|
+
[@loaded.age.class, @loaded.active.class, @loaded.tags[1].class]
|
158
|
+
#=> [Integer, FalseClass, Integer]
|
159
|
+
|
160
|
+
## refresh! maintains type preservation
|
161
|
+
@model = JsonTestModel.new(model_id: @test_id, age: 25, active: true)
|
162
|
+
@model.save
|
163
|
+
# Modify directly in Redis to simulate external change
|
164
|
+
@model.dbclient.hset(@model.dbkey, "age", Familia::JsonSerializer.dump(30))
|
165
|
+
@model.refresh!
|
166
|
+
[@model.age, @model.age.class]
|
167
|
+
#=> [30, Integer]
|
168
|
+
|
169
|
+
## to_h returns string keys (external API compatibility)
|
170
|
+
@model = JsonTestModel.new(model_id: @test_id, name: "API Test", age: 40)
|
171
|
+
@hash = @model.to_h
|
172
|
+
@hash.keys.first.class
|
173
|
+
#=> String
|
174
|
+
|
175
|
+
## to_h has string key for name field
|
176
|
+
@hash.key?("name")
|
177
|
+
#=> true
|
178
|
+
|
179
|
+
## Nil values are handled correctly
|
180
|
+
@model = JsonTestModel.new(model_id: @test_id, name: "Nil Test", age: nil, active: nil)
|
181
|
+
@model.save
|
182
|
+
@loaded = JsonTestModel.find(@test_id)
|
183
|
+
[@loaded.age, @loaded.active]
|
184
|
+
#=> [nil, nil]
|
185
|
+
|
186
|
+
## Empty string preserved correctly
|
187
|
+
@model = JsonTestModel.new(model_id: @test_id, name: "")
|
188
|
+
@model.save
|
189
|
+
@loaded = JsonTestModel.find(@test_id)
|
190
|
+
@loaded.name
|
191
|
+
#=> ""
|
192
|
+
|
193
|
+
## Empty string has String class
|
194
|
+
@loaded.name.class
|
195
|
+
#=> String
|
196
|
+
|
197
|
+
## String fields with numeric values preserve String type (badge "007")
|
198
|
+
@model = JsonTestModel.new(model_id: @test_id, badge_number: "007")
|
199
|
+
@model.save
|
200
|
+
@loaded = JsonTestModel.find(@test_id)
|
201
|
+
@loaded.badge_number
|
202
|
+
#=> "007"
|
203
|
+
|
204
|
+
## Badge number preserves String class (not Integer)
|
205
|
+
@loaded.badge_number.class
|
206
|
+
#=> String
|
207
|
+
|
208
|
+
## String "true" stays String (not Boolean)
|
209
|
+
@model = JsonTestModel.new(model_id: @test_id, status: "true")
|
210
|
+
@model.save
|
211
|
+
@loaded = JsonTestModel.find(@test_id)
|
212
|
+
[@loaded.status, @loaded.status.class]
|
213
|
+
#=> ["true", String]
|
214
|
+
|
215
|
+
## String "false" stays String (not Boolean)
|
216
|
+
@model = JsonTestModel.new(model_id: @test_id, status: "false")
|
217
|
+
@model.save
|
218
|
+
@loaded = JsonTestModel.find(@test_id)
|
219
|
+
[@loaded.status, @loaded.status.class]
|
220
|
+
#=> ["false", String]
|
221
|
+
|
222
|
+
## String "null" stays String (not nil)
|
223
|
+
@model = JsonTestModel.new(model_id: @test_id, placeholder: "null")
|
224
|
+
@model.save
|
225
|
+
@loaded = JsonTestModel.find(@test_id)
|
226
|
+
[@loaded.placeholder, @loaded.placeholder.class]
|
227
|
+
#=> ["null", String]
|
228
|
+
|
229
|
+
## Numeric string "123" preserves String type
|
230
|
+
@model = JsonTestModel.new(model_id: @test_id, badge_number: "123")
|
231
|
+
@model.save
|
232
|
+
@loaded = JsonTestModel.find(@test_id)
|
233
|
+
[@loaded.badge_number, @loaded.badge_number.class]
|
234
|
+
#=> ["123", String]
|
235
|
+
|
236
|
+
## Zero values preserved with correct types
|
237
|
+
@model = JsonTestModel.new(model_id: @test_id, age: 0, score: 0.0)
|
238
|
+
@model.save
|
239
|
+
@loaded = JsonTestModel.find(@test_id)
|
240
|
+
[@loaded.age, @loaded.score]
|
241
|
+
#=> [0, 0.0]
|
242
|
+
|
243
|
+
## Zero Integer and Float have correct classes
|
244
|
+
[@loaded.age.class, @loaded.score.class]
|
245
|
+
#=> [Integer, Float]
|
246
|
+
|
247
|
+
# Teardown: Clean up test data
|
248
|
+
JsonTestModel.destroy!(@test_id) if JsonTestModel.exists?(@test_id)
|
@@ -1,36 +1,42 @@
|
|
1
1
|
# try/horreum/serialization_persistent_fields_try.rb
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative '../../support/helpers/test_helpers'
|
4
4
|
|
5
5
|
Familia.debug = false
|
6
6
|
|
7
|
-
# Test class with mixed field
|
7
|
+
# Test class with mixed field types for serialization
|
8
8
|
class SerializationCategoryTest < Familia::Horreum
|
9
|
+
feature :transient_fields
|
10
|
+
|
9
11
|
identifier_field :id
|
10
12
|
field :id
|
11
13
|
field :name # persistent by default
|
12
|
-
field :email
|
13
|
-
|
14
|
-
field :description
|
15
|
-
|
16
|
-
field :metadata
|
14
|
+
field :email # persistent field
|
15
|
+
transient_field :tryouts_cache_data # should be excluded from serialization
|
16
|
+
field :description # explicitly persistent
|
17
|
+
transient_field :temp_settings # should be excluded
|
18
|
+
field :metadata # explicitly persistent
|
17
19
|
end
|
18
20
|
|
19
21
|
# Class with all transient fields
|
20
22
|
class AllTransientSerializationTest < Familia::Horreum
|
23
|
+
feature :transient_fields
|
24
|
+
|
21
25
|
identifier_field :id
|
22
26
|
field :id
|
23
|
-
|
24
|
-
|
27
|
+
transient_field :temp1
|
28
|
+
transient_field :temp2
|
25
29
|
end
|
26
30
|
|
27
|
-
# Mixed
|
31
|
+
# Mixed field types with aliased fields
|
28
32
|
class AliasedSerializationTest < Familia::Horreum
|
33
|
+
feature :transient_fields
|
34
|
+
|
29
35
|
identifier_field :id
|
30
36
|
field :id
|
31
|
-
field :internal_name, as: :display_name
|
32
|
-
|
33
|
-
field :user_data, as: :data
|
37
|
+
field :internal_name, as: :display_name
|
38
|
+
transient_field :temp_cache, as: :cache
|
39
|
+
field :user_data, as: :data
|
34
40
|
end
|
35
41
|
|
36
42
|
# Setup test instance with all field types
|
@@ -66,7 +72,7 @@ end
|
|
66
72
|
@hash_result.key?("name")
|
67
73
|
#=> true
|
68
74
|
|
69
|
-
## to_h includes
|
75
|
+
## to_h includes regular persistent fields
|
70
76
|
@hash_result.key?("email")
|
71
77
|
#=> true
|
72
78
|
|
@@ -82,9 +88,9 @@ end
|
|
82
88
|
@hash_result.key?(:temp_settings)
|
83
89
|
#=> false
|
84
90
|
|
85
|
-
## to_h
|
86
|
-
@hash_result["metadata"]
|
87
|
-
|
91
|
+
## to_h returns actual Ruby values (Hash, not serialized JSON string)
|
92
|
+
@hash_result["metadata"].class
|
93
|
+
#=> Hash
|
88
94
|
|
89
95
|
## to_a excludes transient fields
|
90
96
|
@array_result = @serialization_test.to_a
|
@@ -133,7 +139,7 @@ SerializationCategoryTest.persistent_fields.include?(:email)
|
|
133
139
|
@all_transient.to_a
|
134
140
|
#=> ["transient_test_1"]
|
135
141
|
|
136
|
-
## Aliased fields serialization uses original field names
|
142
|
+
## Aliased fields serialization uses original field names (transient excluded)
|
137
143
|
@aliased_hash = @aliased_test.to_h
|
138
144
|
@aliased_hash.keys.sort
|
139
145
|
#=> ["id", "internal_name", "user_data"]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# try/horreum/serialization_try.rb
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative '../../support/helpers/test_helpers'
|
4
4
|
|
5
5
|
Familia.debug = false
|
6
6
|
|
@@ -103,9 +103,9 @@ Familia.dbclient.set('debug:ending_save_if_not_exists_tests', Familia.now.to_s)
|
|
103
103
|
[@customer.name, @customer.email]
|
104
104
|
#=> ["Bob Jones", "jane@example.com"]
|
105
105
|
|
106
|
-
## serialize_value handles strings
|
106
|
+
## serialize_value handles strings with JSON encoding
|
107
107
|
@customer.serialize_value('test string')
|
108
|
-
#=> "test string"
|
108
|
+
#=> "\"test string\""
|
109
109
|
|
110
110
|
## serialize_value handles numbers
|
111
111
|
@customer.serialize_value(42)
|
@@ -121,7 +121,7 @@ Familia.dbclient.set('debug:ending_save_if_not_exists_tests', Familia.now.to_s)
|
|
121
121
|
|
122
122
|
## deserialize_value handles JSON strings back to objects
|
123
123
|
@customer.deserialize_value('{"key":"value","num":123}')
|
124
|
-
#=> {
|
124
|
+
#=> {"key"=>"value", "num"=>123}
|
125
125
|
|
126
126
|
## deserialize_value handles JSON arrays
|
127
127
|
@customer.deserialize_value('[1,2,"three"]')
|
data/try/valkey.conf
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# try/test-valkey.conf
|
2
|
+
|
3
|
+
# Familia - Tryouts Valkey Config
|
4
|
+
# 2025-10-01
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
#
|
8
|
+
# $ valkey-server try/valkey.conf
|
9
|
+
#
|
10
|
+
|
11
|
+
dir ./data
|
12
|
+
|
13
|
+
enable-debug-command yes
|
14
|
+
|
15
|
+
#requirepass CHANGEME
|
16
|
+
|
17
|
+
bind 127.0.0.1
|
18
|
+
port 2525
|
19
|
+
databases 10
|
20
|
+
|
21
|
+
timeout 4
|
22
|
+
daemonize no
|
23
|
+
loglevel notice
|
24
|
+
|
25
|
+
# Disable RDB persistence for tests DB
|
26
|
+
save ""
|