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
@@ -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,7 +1,7 @@
|
|
1
1
|
# try/horreum/relations_try.rb
|
2
2
|
# Test Horreum Database type relations functionality
|
3
3
|
|
4
|
-
require_relative '
|
4
|
+
require_relative '../../support/helpers/test_helpers'
|
5
5
|
|
6
6
|
Familia.debug = false
|
7
7
|
|
@@ -92,15 +92,15 @@ prefs = @test_user.preferences
|
|
92
92
|
@test_user.preferences.size
|
93
93
|
#=> 2
|
94
94
|
|
95
|
-
## Clearing a counter returns
|
95
|
+
## Clearing a counter returns 0 when not set yet
|
96
96
|
@test_product.views.clear
|
97
97
|
@test_product.views.clear
|
98
|
-
#=>
|
98
|
+
#=> 0
|
99
99
|
|
100
|
-
## Clearing a counter returns
|
100
|
+
## Clearing a counter returns 1 when it is set
|
101
101
|
@test_product.views.increment
|
102
102
|
@test_product.views.clear
|
103
|
-
#=>
|
103
|
+
#=> 1
|
104
104
|
|
105
105
|
## Counter Database type works
|
106
106
|
@test_product.views.increment
|
@@ -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
|
|
@@ -20,10 +20,10 @@ Familia.dbclient.set('debug:starting_save_if_not_exists_tests', Familia.now.to_s
|
|
20
20
|
@new_customer.save_if_not_exists
|
21
21
|
#=> true
|
22
22
|
|
23
|
-
## save_if_not_exists raises error when customer already exists
|
23
|
+
## save_if_not_exists! raises error when customer already exists
|
24
24
|
@duplicate_customer = Customer.new "new-customer-#{@test_id}@test.com"
|
25
25
|
@duplicate_customer.name = 'Duplicate Customer'
|
26
|
-
@duplicate_customer.save_if_not_exists
|
26
|
+
@duplicate_customer.save_if_not_exists!
|
27
27
|
#=!> Familia::RecordExistsError
|
28
28
|
#==> error.message.include?("Key already exists")
|
29
29
|
|
@@ -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"]')
|