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,172 @@
|
|
1
|
+
# lib/familia/horreum/serialization.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
class Horreum
|
5
|
+
# Serialization - Instance-level methods for object serialization
|
6
|
+
# Handles conversion between Ruby objects and Valkey hash storage
|
7
|
+
module Serialization
|
8
|
+
# Converts the object's persistent fields to a hash for external use.
|
9
|
+
#
|
10
|
+
# Serializes persistent field values for external consumption (APIs, logs),
|
11
|
+
# excluding non-loggable fields like encrypted fields for security.
|
12
|
+
# Only non-nil values are included in the resulting hash.
|
13
|
+
#
|
14
|
+
# @return [Hash] Hash with field names as keys and serialized values
|
15
|
+
# safe for external exposure
|
16
|
+
#
|
17
|
+
# @example Converting an object to hash format for API response
|
18
|
+
# user = User.new(name: "John", email: "john@example.com", age: 30)
|
19
|
+
# user.to_h
|
20
|
+
# # => {"name"=>"John", "email"=>"john@example.com", "age"=>"30"}
|
21
|
+
# # encrypted fields are excluded for security
|
22
|
+
#
|
23
|
+
# @note Only loggable fields are included for security
|
24
|
+
# @note Only fields with non-nil values are included
|
25
|
+
#
|
26
|
+
def to_h
|
27
|
+
self.class.persistent_fields.each_with_object({}) do |field, hsh|
|
28
|
+
field_type = self.class.field_types[field]
|
29
|
+
|
30
|
+
# Security: Skip non-loggable fields (e.g., encrypted fields)
|
31
|
+
next unless field_type.loggable
|
32
|
+
|
33
|
+
method_name = field_type.method_name
|
34
|
+
val = send(method_name)
|
35
|
+
prepared = serialize_value(val)
|
36
|
+
Familia.ld " [to_h] field: #{field} val: #{val.class} prepared: #{prepared&.class || '[nil]'}"
|
37
|
+
|
38
|
+
# Only include non-nil values in the hash for Valkey
|
39
|
+
# Use string key for database compatibility
|
40
|
+
hsh[field.to_s] = prepared unless prepared.nil?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Converts the object's persistent fields to a hash for database storage.
|
45
|
+
#
|
46
|
+
# Serializes ALL persistent field values for database storage, including
|
47
|
+
# encrypted fields. This is used internally by commit_fields and other
|
48
|
+
# persistence operations.
|
49
|
+
#
|
50
|
+
# @return [Hash] Hash with field names as keys and serialized values
|
51
|
+
# ready for database storage
|
52
|
+
#
|
53
|
+
# @note Includes ALL persistent fields, including encrypted fields
|
54
|
+
# @note Only fields with non-nil values are included for storage efficiency
|
55
|
+
#
|
56
|
+
def to_h_for_storage
|
57
|
+
self.class.persistent_fields.each_with_object({}) do |field, hsh|
|
58
|
+
field_type = self.class.field_types[field]
|
59
|
+
method_name = field_type.method_name
|
60
|
+
val = send(method_name)
|
61
|
+
prepared = serialize_value(val)
|
62
|
+
Familia.ld " [to_h_for_storage] field: #{field} val: #{val.class} prepared: #{prepared&.class || '[nil]'}"
|
63
|
+
|
64
|
+
# Only include non-nil values in the hash for Valkey
|
65
|
+
# Use string key for database compatibility
|
66
|
+
hsh[field.to_s] = prepared unless prepared.nil?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Converts the object's persistent fields to an array.
|
71
|
+
#
|
72
|
+
# Serializes all persistent field values in field definition order,
|
73
|
+
# preparing them for Valkey storage. Each value is processed through
|
74
|
+
# the serialization pipeline to ensure Valkey compatibility.
|
75
|
+
#
|
76
|
+
# @return [Array] Array of serialized field values in field order
|
77
|
+
#
|
78
|
+
# @example Converting an object to array format
|
79
|
+
# user = User.new(name: "John", email: "john@example.com", age: 30)
|
80
|
+
# user.to_a
|
81
|
+
# # => ["John", "john@example.com", "30"]
|
82
|
+
#
|
83
|
+
# @note Values are serialized using the same process as other persistence
|
84
|
+
# methods to maintain data consistency across operations.
|
85
|
+
#
|
86
|
+
def to_a
|
87
|
+
self.class.persistent_fields.filter_map do |field|
|
88
|
+
field_type = self.class.field_types[field]
|
89
|
+
|
90
|
+
# Security: Skip non-loggable fields (e.g., encrypted fields)
|
91
|
+
next unless field_type.loggable
|
92
|
+
|
93
|
+
method_name = field_type.method_name
|
94
|
+
val = send(method_name)
|
95
|
+
prepared = serialize_value(val)
|
96
|
+
Familia.ld " [to_a] field: #{field} method: #{method_name} val: #{val.class} prepared: #{prepared.class}"
|
97
|
+
prepared
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Serializes a Ruby object for Valkey storage.
|
102
|
+
#
|
103
|
+
# Converts Ruby objects into the DB-compatible string representations using
|
104
|
+
# the Familia distinguisher for type coercion. Falls back to JSON serialization
|
105
|
+
# for complex types (Hash, Array) when the primary distinguisher returns nil.
|
106
|
+
#
|
107
|
+
# The serialization process:
|
108
|
+
# 1. Attempts conversion using Familia.distinguisher with relaxed type checking
|
109
|
+
# 2. For Hash/Array types that return nil, tries custom dump_method or Familia::JsonSerializer.dump
|
110
|
+
# 3. Logs warnings when serialization fails completely
|
111
|
+
#
|
112
|
+
# @param val [Object] The Ruby object to serialize for Valkey storage
|
113
|
+
#
|
114
|
+
# @return [String, nil] The serialized value ready for Valkey storage, or nil
|
115
|
+
# if serialization failed
|
116
|
+
#
|
117
|
+
# @example Serializing different data types
|
118
|
+
# serialize_value("hello") # => "hello"
|
119
|
+
# serialize_value(42) # => "42"
|
120
|
+
# serialize_value({name: "John"}) # => '{"name":"John"}'
|
121
|
+
# serialize_value([1, 2, 3]) # => "[1,2,3]"
|
122
|
+
#
|
123
|
+
# @note This method integrates with Familia's type system and supports
|
124
|
+
# custom serialization methods when available on the object
|
125
|
+
#
|
126
|
+
# @see Familia.distinguisher The primary serialization mechanism
|
127
|
+
#
|
128
|
+
def serialize_value(val)
|
129
|
+
# Security: Handle ConcealedString safely - extract encrypted data for storage
|
130
|
+
return val.encrypted_value if val.respond_to?(:encrypted_value)
|
131
|
+
|
132
|
+
prepared = Familia.distinguisher(val, strict_values: false)
|
133
|
+
|
134
|
+
# If the distinguisher returns nil, try using the dump_method but only
|
135
|
+
# use JSON serialization for complex types that need it.
|
136
|
+
if prepared.nil? && (val.is_a?(Hash) || val.is_a?(Array))
|
137
|
+
prepared = val.respond_to?(dump_method) ? val.send(dump_method) : Familia::JsonSerializer.dump(val)
|
138
|
+
end
|
139
|
+
|
140
|
+
# If both the distinguisher and dump_method return nil, log an error
|
141
|
+
Familia.ld "[#{self.class}#serialize_value] nil returned for #{self.class}" if prepared.nil?
|
142
|
+
|
143
|
+
prepared
|
144
|
+
end
|
145
|
+
|
146
|
+
# Converts a Database string value back to its original Ruby type
|
147
|
+
#
|
148
|
+
# This method attempts to deserialize JSON strings back to their original
|
149
|
+
# Hash or Array types. Simple string values are returned as-is.
|
150
|
+
#
|
151
|
+
# @param val [String] The string value from Database to deserialize
|
152
|
+
# @param symbolize [Boolean] Whether to symbolize hash keys (default: true for compatibility)
|
153
|
+
# @return [Object] The deserialized value (Hash, Array, or original string)
|
154
|
+
#
|
155
|
+
def deserialize_value(val, symbolize: true)
|
156
|
+
return val if val.nil? || val == ''
|
157
|
+
|
158
|
+
# Try to parse as JSON first for complex types
|
159
|
+
begin
|
160
|
+
parsed = Familia::JsonSerializer.parse(val, symbolize_names: symbolize)
|
161
|
+
# Only return parsed value if it's a complex type (Hash/Array)
|
162
|
+
# Simple values should remain as strings
|
163
|
+
return parsed if parsed.is_a?(Hash) || parsed.is_a?(Array)
|
164
|
+
rescue Familia::SerializerError
|
165
|
+
# Not valid JSON, return as-is
|
166
|
+
end
|
167
|
+
|
168
|
+
val
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -7,7 +7,8 @@ module Familia
|
|
7
7
|
# instance-level functionality for Database operations and object management.
|
8
8
|
#
|
9
9
|
class Horreum
|
10
|
-
# Settings -
|
10
|
+
# Settings - Instance-level configuration methods for Horreum models
|
11
|
+
# Provides per-instance settings like logical_database, dump_method, load_method, suffix
|
11
12
|
#
|
12
13
|
module Settings
|
13
14
|
attr_writer :dump_method, :load_method, :suffix
|
@@ -7,7 +7,8 @@ module Familia
|
|
7
7
|
# instance-level functionality for Database operations and object management.
|
8
8
|
#
|
9
9
|
class Horreum
|
10
|
-
# Utils -
|
10
|
+
# Utils - Instance-level utility methods for Familia::Horreum
|
11
|
+
# Provides identifier handling, dbkey generation, and object inspection
|
11
12
|
#
|
12
13
|
module Utils
|
13
14
|
# def uri
|
data/lib/familia/horreum.rb
CHANGED
@@ -1,100 +1,193 @@
|
|
1
1
|
# lib/familia/horreum.rb
|
2
2
|
|
3
|
-
require_relative 'horreum/
|
4
|
-
require_relative 'horreum/
|
5
|
-
require_relative 'horreum/
|
6
|
-
require_relative 'horreum/
|
3
|
+
require_relative 'horreum/settings'
|
4
|
+
require_relative 'horreum/connection'
|
5
|
+
require_relative 'horreum/database_commands'
|
6
|
+
require_relative 'horreum/related_fields'
|
7
|
+
require_relative 'horreum/definition'
|
8
|
+
require_relative 'horreum/management'
|
9
|
+
require_relative 'horreum/persistence'
|
10
|
+
require_relative 'horreum/serialization'
|
11
|
+
require_relative 'horreum/utils'
|
7
12
|
|
8
13
|
module Familia
|
9
14
|
#
|
10
|
-
# Horreum: A
|
15
|
+
# Horreum: A Valkey/Redis-backed ORM base class providing field definitions and DataType relationships
|
11
16
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
# * Uses 'hashkey' to define a Database hash referred to as "object"
|
16
|
-
# * Applies a default expiry (5 years) to all keys
|
17
|
+
# Familia::Horreum serves as the foundation for creating Ruby objects that are persisted
|
18
|
+
# to and retrieved from Valkey/Redis. It provides a comprehensive field system, DataType
|
19
|
+
# relationships, and automated key generation for seamless object-relational mapping.
|
17
20
|
#
|
18
|
-
#
|
19
|
-
# *
|
20
|
-
# *
|
21
|
-
#
|
21
|
+
# Core Features:
|
22
|
+
# * Field definition system with automatic getter/setter/fast writer generation
|
23
|
+
# * DataType relationships (sets, lists, hashes, sorted sets, counters, locks)
|
24
|
+
# * Flexible identifier strategies (symbols, procs, arrays)
|
25
|
+
# * Automatic Redis key generation and management
|
26
|
+
# * Feature system for modular functionality (expiration, safe_dump, relationships)
|
27
|
+
# * Thread-safe DataType instances with automatic freezing
|
28
|
+
# * Multiple initialization patterns for different use cases
|
29
|
+
#
|
30
|
+
# Architecture:
|
31
|
+
# * Inheriting classes automatically extend definition and management methods
|
32
|
+
# * Instance-level DataTypes are created per object with unique Redis keys
|
33
|
+
# * Class-level DataTypes are shared across all instances
|
34
|
+
# * All objects tracked in Familia.members for reloading and introspection
|
22
35
|
#
|
23
36
|
# Usage:
|
24
|
-
# class
|
37
|
+
# class User < Familia::Horreum
|
38
|
+
# identifier_field :email
|
25
39
|
# field :name
|
26
|
-
# field :
|
40
|
+
# field :created_at
|
41
|
+
# set :tags
|
42
|
+
# list :activity_log
|
43
|
+
# feature :expiration
|
27
44
|
# end
|
28
45
|
#
|
46
|
+
# user = User.new(email: "john@example.com", name: "John")
|
47
|
+
# user.tags << "premium"
|
48
|
+
# user.save
|
49
|
+
#
|
29
50
|
class Horreum
|
30
51
|
include Familia::Base
|
31
|
-
include Familia::Horreum::
|
52
|
+
include Familia::Horreum::Persistence
|
53
|
+
include Familia::Horreum::Serialization
|
54
|
+
include Familia::Horreum::Connection
|
55
|
+
include Familia::Horreum::DatabaseCommands
|
32
56
|
include Familia::Horreum::Settings
|
57
|
+
include Familia::Horreum::Utils
|
33
58
|
|
34
59
|
using Familia::Refinements::TimeLiterals
|
35
60
|
|
36
|
-
#
|
61
|
+
# Class-Level Inheritance and Extension Management
|
37
62
|
#
|
38
|
-
#
|
39
|
-
#
|
63
|
+
# This singleton class block defines the metaclass behavior that governs how
|
64
|
+
# Familia::Horreum subclasses are configured and extended when they inherit.
|
40
65
|
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
66
|
+
# Key Responsibilities:
|
67
|
+
# * Automatically extend subclasses with essential functionality modules
|
68
|
+
# * Register new classes in Familia.members for tracking and reloading
|
69
|
+
# * Provide class-level attribute accessors for configuration
|
70
|
+
# * Establish the inheritance chain that enables the Horreum ORM pattern
|
44
71
|
#
|
45
|
-
#
|
46
|
-
# *
|
47
|
-
# *
|
48
|
-
# *
|
49
|
-
# *
|
72
|
+
# When a class inherits from Horreum, the inherited() hook automatically:
|
73
|
+
# * Extends DefinitionMethods (field, identifier_field, dbkey definitions)
|
74
|
+
# * Extends ManagementMethods (create, find, destroy operations)
|
75
|
+
# * Extends Connection (database client and connection management)
|
76
|
+
# * Extends Features (feature loading and configuration)
|
77
|
+
# * Registers the class in Familia.members for introspection
|
50
78
|
#
|
51
|
-
#
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
56
|
-
# end
|
57
|
-
# end
|
58
|
-
# end
|
59
|
-
#
|
60
|
-
# MyClass.class_method # => "This is a class method"
|
61
|
-
#
|
62
|
-
# Note: Changes made here affect the class itself and all future instances,
|
63
|
-
# but not existing instances of the class.
|
79
|
+
# Class-Level Attributes:
|
80
|
+
# * @parent - Parent object reference for nested relationships
|
81
|
+
# * @dbclient - Database connection override for this class
|
82
|
+
# * @dump_method/@load_method - Serialization method configuration
|
83
|
+
# * @has_related_fields - Flag indicating if DataType relationships are defined
|
64
84
|
#
|
65
85
|
class << self
|
66
86
|
attr_accessor :parent
|
67
87
|
# TODO: Where are we calling dbclient= from now with connection pool?
|
68
88
|
attr_writer :dbclient, :dump_method, :load_method
|
69
|
-
attr_reader :
|
89
|
+
attr_reader :has_related_fields
|
70
90
|
|
71
91
|
# Extends ClassMethods to subclasses and tracks Familia members
|
72
92
|
def inherited(member)
|
73
|
-
Familia.trace :HORREUM, nil, "Welcome #{member} to the family"
|
93
|
+
Familia.trace :HORREUM, nil, "Welcome #{member} to the family" if Familia.debug?
|
74
94
|
|
75
95
|
# Class-level functionality extensions:
|
76
96
|
member.extend(Familia::Horreum::DefinitionMethods) # field(), identifier_field(), dbkey()
|
77
97
|
member.extend(Familia::Horreum::ManagementMethods) # create(), find(), destroy!()
|
78
98
|
member.extend(Familia::Horreum::Connection) # dbclient, connection management
|
79
|
-
member.extend(Familia::Features)
|
99
|
+
member.extend(Familia::Features) # feature() method for optional modules
|
100
|
+
|
101
|
+
# Copy parent class configuration to child class
|
102
|
+
# This implements conventional ORM inheritance behavior where child classes
|
103
|
+
# automatically inherit all parent configuration without manual copying
|
104
|
+
parent_class = member.superclass
|
105
|
+
if parent_class.respond_to?(:identifier_field) && parent_class != Familia::Horreum
|
106
|
+
# Copy essential configuration instance variables from parent
|
107
|
+
if parent_class.identifier_field
|
108
|
+
member.instance_variable_set(:@identifier_field, parent_class.identifier_field)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Copy field system configuration
|
112
|
+
member.instance_variable_set(:@fields, parent_class.fields.dup) if parent_class.fields&.any?
|
113
|
+
|
114
|
+
if parent_class.respond_to?(:field_types) && parent_class.field_types&.any?
|
115
|
+
# Copy field_types hash (FieldType instances are frozen/immutable and can be safely shared)
|
116
|
+
copied_field_types = parent_class.field_types.dup
|
117
|
+
member.instance_variable_set(:@field_types, copied_field_types)
|
118
|
+
# Re-install field methods on the child class using proper method name detection
|
119
|
+
parent_class.field_types.each_value do |field_type|
|
120
|
+
# Collect all method names that field_type.install will create
|
121
|
+
methods_to_check = [
|
122
|
+
field_type.method_name,
|
123
|
+
(field_type.method_name ? :"#{field_type.method_name}=" : nil),
|
124
|
+
field_type.fast_method_name,
|
125
|
+
].compact
|
126
|
+
|
127
|
+
# Only install if none of the methods already exist
|
128
|
+
methods_exist = methods_to_check.any? do |method_name|
|
129
|
+
member.method_defined?(method_name) || member.private_method_defined?(method_name)
|
130
|
+
end
|
131
|
+
|
132
|
+
field_type.install(member) unless methods_exist
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Copy features configuration
|
137
|
+
if parent_class.respond_to?(:features_enabled) && parent_class.features_enabled&.any?
|
138
|
+
member.instance_variable_set(:@features_enabled, parent_class.features_enabled.dup)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Copy other configuration using consistent instance variable access
|
142
|
+
if (prefix = parent_class.instance_variable_get(:@prefix))
|
143
|
+
member.instance_variable_set(:@prefix, prefix)
|
144
|
+
end
|
145
|
+
if (suffix = parent_class.instance_variable_get(:@suffix))
|
146
|
+
member.instance_variable_set(:@suffix, suffix)
|
147
|
+
end
|
148
|
+
if (logical_db = parent_class.instance_variable_get(:@logical_database))
|
149
|
+
member.instance_variable_set(:@logical_database, logical_db)
|
150
|
+
end
|
151
|
+
if (default_exp = parent_class.instance_variable_get(:@default_expiration))
|
152
|
+
member.instance_variable_set(:@default_expiration, default_exp)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Copy DataType relationships
|
156
|
+
if parent_class.class_related_fields&.any?
|
157
|
+
member.instance_variable_set(:@class_related_fields, parent_class.class_related_fields.dup)
|
158
|
+
end
|
159
|
+
if parent_class.related_fields&.any?
|
160
|
+
member.instance_variable_set(:@related_fields, parent_class.related_fields.dup)
|
161
|
+
end
|
162
|
+
if parent_class.instance_variable_get(:@has_related_fields)
|
163
|
+
member.instance_variable_set(:@has_related_fields,
|
164
|
+
parent_class.instance_variable_get(:@has_related_fields))
|
165
|
+
end
|
166
|
+
end
|
80
167
|
|
81
168
|
# Track all classes that inherit from Horreum
|
82
169
|
Familia.members << member
|
170
|
+
|
171
|
+
# Set up automatic instance tracking using built-in class_sorted_set
|
172
|
+
member.class_sorted_set :instances
|
173
|
+
|
83
174
|
super
|
84
175
|
end
|
85
176
|
end
|
86
177
|
|
178
|
+
attr_writer :dbclient
|
179
|
+
|
87
180
|
# Instance initialization
|
88
|
-
# This method sets up the object's state, including Redis-related data.
|
181
|
+
# This method sets up the object's state, including Valkey/Redis-related data.
|
89
182
|
#
|
90
183
|
# Usage:
|
91
184
|
#
|
92
|
-
# Session.new("abc123", "user456") # positional (brittle)
|
93
|
-
# Session.new(sessid: "abc123", custid: "user456") # hash (robust)
|
94
|
-
# Session.new({sessid: "abc123", custid: "user456"}) # legacy hash (robust)
|
185
|
+
# `Session.new("abc123", "user456")` # positional (brittle)
|
186
|
+
# `Session.new(sessid: "abc123", custid: "user456")` # hash (robust)
|
187
|
+
# `Session.new({sessid: "abc123", custid: "user456"})` # legacy hash (robust)
|
95
188
|
#
|
96
189
|
def initialize(*args, **kwargs)
|
97
|
-
Familia.trace :INITIALIZE,
|
190
|
+
Familia.trace :INITIALIZE, nil, "Initializing #{self.class}" if Familia.debug?
|
98
191
|
initialize_relatives
|
99
192
|
|
100
193
|
# No longer auto-create a key field - the identifier method will
|
@@ -108,15 +201,16 @@ module Familia
|
|
108
201
|
|
109
202
|
# Initialize object with arguments using one of four strategies:
|
110
203
|
#
|
111
|
-
# 1. **Identifier** (Recommended for lookups): A single argument is
|
112
|
-
#
|
113
|
-
#
|
204
|
+
# 1. **Identifier** (Recommended for lookups): A single argument is
|
205
|
+
# treated as the identifier. Robust and convenient for creating
|
206
|
+
# objects from an ID. e.g. `Customer.new("cust_123")`
|
114
207
|
#
|
115
|
-
# 2. **Keyword Arguments** (Recommended for creation): Order-independent
|
116
|
-
#
|
208
|
+
# 2. **Keyword Arguments** (Recommended for creation): Order-independent
|
209
|
+
# field assignment
|
210
|
+
# e.g. Customer.new(name: "John", email: "john@example.com")
|
117
211
|
#
|
118
212
|
# 3. **Positional Arguments** (Legacy): Field assignment by definition order
|
119
|
-
#
|
213
|
+
# e.g. Customer.new("cust_123", "John", "john@example.com")
|
120
214
|
#
|
121
215
|
# 4. **No Arguments**: Object created with all fields as nil
|
122
216
|
#
|
@@ -127,8 +221,8 @@ module Familia
|
|
127
221
|
initialize_with_keyword_args(**kwargs)
|
128
222
|
elsif args.any?
|
129
223
|
initialize_with_positional_args(*args)
|
130
|
-
|
131
|
-
Familia.trace :INITIALIZE,
|
224
|
+
elsif Familia.debug?
|
225
|
+
Familia.trace :INITIALIZE, nil, "#{self.class} initialized with no arguments"
|
132
226
|
# Default values are intentionally NOT set here
|
133
227
|
end
|
134
228
|
|
@@ -138,16 +232,27 @@ module Familia
|
|
138
232
|
init
|
139
233
|
end
|
140
234
|
|
235
|
+
# Override this method in subclasses for custom initialization logic.
|
236
|
+
# This is called AFTER fields are set and relatives are initialized.
|
237
|
+
#
|
238
|
+
# DO NOT override initialize() - use this init() hook instead.
|
239
|
+
#
|
240
|
+
# Example:
|
241
|
+
# def init(name = nil)
|
242
|
+
# @name = name || SecureRandom.hex(4)
|
243
|
+
# end
|
141
244
|
def init(*args, **kwargs)
|
142
245
|
# Default no-op
|
143
246
|
end
|
144
247
|
|
145
248
|
# Sets up related Database objects for the instance
|
146
|
-
# This method is crucial for establishing Redis-based relationships
|
249
|
+
# This method is crucial for establishing Valkey/Redis-based relationships
|
147
250
|
#
|
148
251
|
# This needs to be called in the initialize method.
|
149
252
|
#
|
150
253
|
def initialize_relatives
|
254
|
+
# Store initialization flag on singleton class to avoid polluting instance variables
|
255
|
+
return if singleton_class.instance_variable_defined?(:"@relatives_initialized")
|
151
256
|
# Generate instances of each DataType. These need to be
|
152
257
|
# unique for each instance of this class so they can piggyback
|
153
258
|
# on the specifc index of this instance.
|
@@ -159,7 +264,7 @@ module Familia
|
|
159
264
|
self.class.related_fields.each_pair do |name, data_type_definition|
|
160
265
|
klass = data_type_definition.klass
|
161
266
|
opts = data_type_definition.opts
|
162
|
-
Familia.trace :INITIALIZE_RELATIVES,
|
267
|
+
Familia.trace :INITIALIZE_RELATIVES, nil, "#{name} => #{klass} #{opts.keys}" if Familia.debug?
|
163
268
|
|
164
269
|
# As a subclass of Familia::Horreum, we add ourselves as the parent
|
165
270
|
# automatically. This is what determines the dbkey for DataType
|
@@ -169,7 +274,8 @@ module Familia
|
|
169
274
|
# then the dbkey for this DataType instance will be
|
170
275
|
# `customer:customer_id:name`.
|
171
276
|
#
|
172
|
-
|
277
|
+
# Store reference to the instance for lazy ParentDefinition creation
|
278
|
+
opts[:parent] = self
|
173
279
|
|
174
280
|
suffix_override = opts.fetch(:suffix, name)
|
175
281
|
|
@@ -186,49 +292,10 @@ module Familia
|
|
186
292
|
# e.g. customer.name #=> `#<Familia::HashKey:0x0000...>`
|
187
293
|
instance_variable_set :"@#{name}", related_object
|
188
294
|
end
|
189
|
-
end
|
190
295
|
|
191
|
-
|
192
|
-
|
193
|
-
#
|
194
|
-
# @param args [Array] List of values to be assigned to fields
|
195
|
-
# @return [Array<Symbol>] List of field names that were successfully updated
|
196
|
-
# (i.e., had non-nil values assigned)
|
197
|
-
# @private
|
198
|
-
def initialize_with_positional_args(*args)
|
199
|
-
Familia.trace :INITIALIZE_ARGS, dbclient, args, caller(1..1) if Familia.debug?
|
200
|
-
self.class.fields.zip(args).filter_map do |field, value|
|
201
|
-
if value
|
202
|
-
send(:"#{field}=", value)
|
203
|
-
field.to_sym
|
204
|
-
end
|
205
|
-
end
|
296
|
+
# Mark relatives as initialized on singleton class to avoid polluting instance variables
|
297
|
+
singleton_class.instance_variable_set(:"@relatives_initialized", true)
|
206
298
|
end
|
207
|
-
private :initialize_with_positional_args
|
208
|
-
|
209
|
-
# Initializes the object with keyword arguments.
|
210
|
-
# Assigns values to fields based on the provided hash of field names and values.
|
211
|
-
# Handles both symbol and string keys to accommodate different sources of data.
|
212
|
-
#
|
213
|
-
# @param fields [Hash] Hash of field names (as symbols or strings) and their values
|
214
|
-
# @return [Array<Symbol>] List of field names that were successfully updated
|
215
|
-
# (i.e., had non-nil values assigned)
|
216
|
-
# @private
|
217
|
-
def initialize_with_keyword_args(**fields)
|
218
|
-
Familia.trace :INITIALIZE_KWARGS, dbclient, fields.keys, caller(1..1) if Familia.debug?
|
219
|
-
self.class.fields.filter_map do |field|
|
220
|
-
# Database will give us field names as strings back, but internally
|
221
|
-
# we use symbols. So we check for both.
|
222
|
-
value = fields[field.to_sym] || fields[field.to_s]
|
223
|
-
if value
|
224
|
-
# Use the mapped method name, not the field name
|
225
|
-
method_name = self.class.field_method_map[field] || field
|
226
|
-
send(:"#{method_name}=", value)
|
227
|
-
field.to_sym
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
private :initialize_with_keyword_args
|
232
299
|
|
233
300
|
def initialize_with_keyword_args_deserialize_value(**fields)
|
234
301
|
# Deserialize Database string values back to their original types
|
@@ -267,36 +334,28 @@ module Familia
|
|
267
334
|
send(definition)
|
268
335
|
when Proc
|
269
336
|
definition.call(self)
|
270
|
-
|
337
|
+
end
|
271
338
|
|
272
339
|
# Return nil for unpopulated identifiers (like unsaved ActiveRecord objects)
|
273
|
-
# Only raise errors when the identifier is actually needed for
|
340
|
+
# Only raise errors when the identifier is actually needed for db operations
|
274
341
|
return nil if unique_id.nil? || unique_id.to_s.empty?
|
275
342
|
|
276
343
|
unique_id
|
277
344
|
end
|
278
345
|
|
279
|
-
|
280
|
-
|
281
|
-
# Summon the mystical Database connection from the depths of instance or class.
|
346
|
+
# Returns the Database connection for the instance using Chain of Responsibility pattern.
|
282
347
|
#
|
283
|
-
# This method
|
284
|
-
#
|
285
|
-
#
|
348
|
+
# This method uses a chain of handlers to resolve connections in priority order:
|
349
|
+
# 1. FiberTransactionHandler - Fiber[:familia_transaction] (active transaction)
|
350
|
+
# 2. CachedConnectionHandler - Accesses self.dbclient
|
351
|
+
# 3. CachedConnectionHandler - Accesses self.class.dbclient
|
352
|
+
# 4. GlobalFallbackHandler - Familia.dbclient(uri || logical_database) (global fallback)
|
286
353
|
#
|
287
|
-
# @return [Redis]
|
354
|
+
# @return [Redis] the Database connection instance.
|
288
355
|
#
|
289
|
-
# @example Finding your Database way
|
290
|
-
# puts object.dbclient
|
291
|
-
# # => #<Redis client v5.4.1 for redis://localhost:6379/0>
|
292
|
-
#
|
293
|
-
def dbclient
|
294
|
-
Fiber[:familia_transaction] || @dbclient || self.class.dbclient
|
295
|
-
# conn.select(self.class.logical_database)
|
296
|
-
end
|
297
356
|
|
298
357
|
def generate_id
|
299
|
-
@objid ||= Familia.generate_id
|
358
|
+
@objid ||= Familia.generate_id
|
300
359
|
end
|
301
360
|
|
302
361
|
# The principle is: **If Familia objects have `to_s`, then they should work
|
@@ -309,5 +368,49 @@ module Familia
|
|
309
368
|
|
310
369
|
identifier.to_s
|
311
370
|
end
|
371
|
+
|
372
|
+
private
|
373
|
+
|
374
|
+
# Initializes the object with positional arguments.
|
375
|
+
# Maps each argument to a corresponding field in the order they are defined.
|
376
|
+
#
|
377
|
+
# @param args [Array] List of values to be assigned to fields
|
378
|
+
# @return [Array<Symbol>] List of field names that were successfully updated
|
379
|
+
# (i.e., had non-nil values assigned)
|
380
|
+
# @private
|
381
|
+
def initialize_with_positional_args(*args)
|
382
|
+
Familia.trace :INITIALIZE_ARGS, nil, args if Familia.debug?
|
383
|
+
self.class.fields.zip(args).filter_map do |field, value|
|
384
|
+
if value
|
385
|
+
send(:"#{field}=", value)
|
386
|
+
field.to_sym
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
# Initializes the object with keyword arguments.
|
392
|
+
# Assigns values to fields based on the provided hash of field names and values.
|
393
|
+
# Handles both symbol and string keys to accommodate different sources of data.
|
394
|
+
#
|
395
|
+
# @param fields [Hash] Hash of field names (as symbols or strings) and their values
|
396
|
+
# @return [Array<Symbol>] List of field names that were successfully updated
|
397
|
+
# (i.e., had non-nil values assigned)
|
398
|
+
# @private
|
399
|
+
def initialize_with_keyword_args(**fields)
|
400
|
+
Familia.trace :INITIALIZE_KWARGS, nil, fields.keys if Familia.debug?
|
401
|
+
self.class.fields.filter_map do |field|
|
402
|
+
# Database will give us field names as strings back, but internally
|
403
|
+
# we use symbols. So we check for both.
|
404
|
+
value = fields[field.to_sym] || fields[field.to_s]
|
405
|
+
if value
|
406
|
+
# Use the mapped method name, not the field name
|
407
|
+
method_name = self.class.field_method_map[field] || field
|
408
|
+
send(:"#{method_name}=", value)
|
409
|
+
field.to_sym
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Builds the instance-level connection chain with handlers in priority order
|
312
415
|
end
|
313
416
|
end
|