familia 2.0.0.pre15 → 2.0.0.pre16
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/code-quality.yml +138 -0
- data/.github/workflows/code-smellage.yml +145 -0
- data/.github/workflows/docs.yml +31 -8
- data/.gitignore +1 -1
- data/.pre-commit-config.yaml +7 -1
- data/.reek.yml +98 -0
- data/.rubocop.yml +48 -10
- data/.talismanrc +9 -0
- data/.yardopts +18 -13
- data/CHANGELOG.rst +64 -4
- data/CLAUDE.md +1 -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 +41 -41
- 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 +623 -19
- 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 +6 -6
- data/examples/single_connection_transaction_confusions.rb +379 -0
- data/lib/familia/base.rb +49 -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/commands.rb +53 -51
- data/lib/familia/data_type/serialization.rb +108 -107
- data/lib/familia/data_type/types/counter.rb +1 -1
- data/lib/familia/data_type/types/hashkey.rb +13 -10
- 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 +26 -15
- data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
- data/lib/familia/data_type/types/unsorted_set.rb +20 -27
- data/lib/familia/data_type.rb +75 -47
- 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 +66 -64
- data/lib/familia/features/expiration/extensions.rb +1 -1
- data/lib/familia/features/expiration.rb +31 -26
- data/lib/familia/features/external_identifier.rb +9 -12
- data/lib/familia/features/object_identifier.rb +56 -19
- 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 +301 -0
- data/lib/familia/features/relationships/indexing.rb +176 -256
- data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
- data/lib/familia/features/relationships/participation/participant_methods.rb +160 -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 +3 -5
- data/lib/familia/features.rb +4 -13
- data/lib/familia/field_type.rb +24 -4
- data/lib/familia/horreum/core/connection.rb +229 -26
- data/lib/familia/horreum/core/database_commands.rb +27 -17
- data/lib/familia/horreum/core/serialization.rb +40 -20
- data/lib/familia/horreum/core/utils.rb +2 -1
- data/lib/familia/horreum/shared/settings.rb +2 -1
- data/lib/familia/horreum/subclass/definition.rb +33 -45
- data/lib/familia/horreum/subclass/management.rb +72 -24
- data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
- data/lib/familia/horreum.rb +196 -114
- 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 +1 -1
- 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 +7 -7
- 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 +10 -10
- data/try/core/errors_try.rb +8 -11
- data/try/core/familia_extended_try.rb +2 -2
- data/try/core/familia_members_methods_try.rb +76 -0
- 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 +1 -1
- 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/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/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 +433 -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 +1 -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 +3 -3
- data/try/horreum/base_try.rb +3 -2
- data/try/horreum/commands_try.rb +1 -1
- data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -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/integration/cross_component_try.rb +3 -3
- data/try/memory/memory_basic_test.rb +1 -1
- data/try/memory/memory_docker_ruby_dump.sh +1 -1
- data/try/models/customer_safe_dump_try.rb +1 -1
- data/try/models/customer_try.rb +8 -10
- 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
- metadata +75 -43
- 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/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
@@ -2,25 +2,61 @@
|
|
2
2
|
|
3
3
|
module Familia
|
4
4
|
class Horreum
|
5
|
-
# Connection
|
6
|
-
# Provides
|
5
|
+
# Connection - Mixed instance and class-level methods for Valkey connection management
|
6
|
+
# Provides connection handling, transactions, and URI normalization for both
|
7
|
+
# class-level operations (e.g., Customer.dbclient) and instance-level operations
|
8
|
+
# (e.g., customer.dbclient)
|
7
9
|
module Connection
|
8
10
|
attr_reader :uri
|
9
11
|
|
10
|
-
#
|
12
|
+
# Normalizes various URI formats to a consistent URI object
|
13
|
+
# Considers the class/instance logical_database when uri is nil or Integer
|
14
|
+
def normalize_uri(uri)
|
15
|
+
case uri
|
16
|
+
when Integer
|
17
|
+
new_uri = Familia.uri.dup
|
18
|
+
new_uri.db = uri
|
19
|
+
new_uri
|
20
|
+
when ->(obj) { obj.is_a?(String) || obj.instance_of?(::String) }
|
21
|
+
URI.parse(uri)
|
22
|
+
when URI
|
23
|
+
uri
|
24
|
+
when nil
|
25
|
+
# Use logical_database if available, otherwise fall back to Familia.uri
|
26
|
+
if respond_to?(:logical_database) && logical_database
|
27
|
+
new_uri = Familia.uri.dup
|
28
|
+
new_uri.db = logical_database
|
29
|
+
new_uri
|
30
|
+
else
|
31
|
+
Familia.uri
|
32
|
+
end
|
33
|
+
else
|
34
|
+
raise ArgumentError, "Invalid URI type: #{uri.class.name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates a new Database connection instance using the class/instance configuration
|
39
|
+
def create_dbclient(uri = nil)
|
40
|
+
parsed_uri = normalize_uri(uri)
|
41
|
+
Familia.create_dbclient(parsed_uri)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the Database connection for the class using Chain of Responsibility pattern.
|
11
45
|
#
|
12
|
-
# This method
|
13
|
-
#
|
14
|
-
#
|
46
|
+
# This method uses a chain of handlers to resolve connections in priority order:
|
47
|
+
# 1. FiberTransactionHandler - Fiber[:familia_transaction] (active transaction)
|
48
|
+
# 2. DefaultConnectionHandler - Horreum model class-level @dbclient
|
49
|
+
# 3. GlobalFallbackHandler - Familia.dbclient(uri || logical_database) (global fallback)
|
15
50
|
#
|
16
51
|
# @return [Redis] the Database connection instance.
|
17
52
|
#
|
18
|
-
def dbclient
|
19
|
-
|
53
|
+
def dbclient(uri = nil)
|
54
|
+
@class_connection_chain ||= build_connection_chain
|
55
|
+
@class_connection_chain.handle(uri)
|
20
56
|
end
|
21
57
|
|
22
58
|
def connect(*)
|
23
|
-
|
59
|
+
create_dbclient(*)
|
24
60
|
end
|
25
61
|
|
26
62
|
def uri=(uri)
|
@@ -46,27 +82,194 @@ module Familia
|
|
46
82
|
#
|
47
83
|
# @note This method works with the global Familia.transaction context when available
|
48
84
|
#
|
85
|
+
# Executes a Redis transaction (MULTI/EXEC) using this object's connection context.
|
86
|
+
#
|
87
|
+
# Provides atomic execution of multiple Redis commands with automatic connection
|
88
|
+
# management and operation mode enforcement. Uses the object's database and
|
89
|
+
# connection settings. Returns a MultiResult object for consistency with global methods.
|
90
|
+
#
|
91
|
+
# @param [Proc] block The block containing Redis commands to execute atomically
|
92
|
+
# @yield [Redis] conn The Redis connection configured for transaction mode
|
93
|
+
# @return [MultiResult] Result object with success status and command results
|
94
|
+
#
|
95
|
+
# @raise [Familia::OperationModeError] When called with incompatible connection handlers
|
96
|
+
# (e.g., FiberConnectionHandler or DefaultConnectionHandler that don't support transactions)
|
97
|
+
#
|
98
|
+
# @example Basic instance transaction
|
99
|
+
# customer = Customer.new(custid: 'cust_123')
|
100
|
+
# result = customer.transaction do |conn|
|
101
|
+
# conn.hset(customer.dbkey, 'name', 'John Doe')
|
102
|
+
# conn.hset(customer.dbkey, 'email', 'john@example.com')
|
103
|
+
# conn.hget(customer.dbkey, 'name')
|
104
|
+
# end
|
105
|
+
# result.successful? # => true
|
106
|
+
# result.results # => ["OK", "OK", "John Doe"]
|
107
|
+
#
|
108
|
+
# @example Using with object's database context
|
109
|
+
# class Customer < Familia::Horreum
|
110
|
+
# logical_database 5 # Use database 5
|
111
|
+
# field :name
|
112
|
+
# field :email
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# customer = Customer.new(custid: 'cust_456')
|
116
|
+
# result = customer.transaction do |conn|
|
117
|
+
# # Commands automatically execute in database 5
|
118
|
+
# conn.hset(customer.dbkey, 'status', 'active')
|
119
|
+
# conn.sadd('active_customers', customer.identifier)
|
120
|
+
# end
|
121
|
+
# result.successful? # => true
|
122
|
+
#
|
123
|
+
# @example Reentrant behavior with global transactions
|
124
|
+
# customer = Customer.new(custid: 'cust_789')
|
125
|
+
#
|
126
|
+
# # When called within a global transaction, reuses the transaction connection
|
127
|
+
# result = Familia.transaction do |global_conn|
|
128
|
+
# global_conn.set('global_key', 'value')
|
129
|
+
#
|
130
|
+
# # This reuses the same transaction connection
|
131
|
+
# customer.transaction do |local_conn|
|
132
|
+
# local_conn.hset(customer.dbkey, 'updated', Time.now.to_i)
|
133
|
+
# 'local_return_value' # Returned directly in nested context
|
134
|
+
# end
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# @note Connection Inheritance:
|
138
|
+
# - Uses object's logical_database setting if configured
|
139
|
+
# - Inherits class-level database settings
|
140
|
+
# - Falls back to instance-level dbclient if set
|
141
|
+
# - Uses global connection chain as final fallback
|
142
|
+
#
|
143
|
+
# @note Transaction Context:
|
144
|
+
# - When called outside global transaction: Creates local MultiResult
|
145
|
+
# - When called inside global transaction: Yields to existing transaction
|
146
|
+
# - Maintains proper Fiber-local state for nested calls
|
147
|
+
#
|
148
|
+
# @see Familia.transaction For global transaction method
|
149
|
+
# @see MultiResult For details on the return value structure
|
150
|
+
# @see #batch_update For similar atomic field updates with MultiResult
|
49
151
|
def transaction(&)
|
50
|
-
|
51
|
-
if Fiber[:familia_transaction]
|
52
|
-
yield(Fiber[:familia_transaction])
|
53
|
-
else
|
54
|
-
# Otherwise, create a local transaction
|
55
|
-
block_result = dbclient.multi(&)
|
56
|
-
end
|
57
|
-
block_result
|
152
|
+
Familia::Connection::TransactionCore.execute_transaction(-> { dbclient }, &)
|
58
153
|
end
|
59
154
|
alias multi transaction
|
60
155
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
156
|
+
# Executes Redis commands in a pipeline using this object's connection context.
|
157
|
+
#
|
158
|
+
# Batches multiple Redis commands together and sends them in a single network
|
159
|
+
# round-trip for improved performance. Uses the object's database and connection
|
160
|
+
# settings. Returns a MultiResult object for consistency with global methods.
|
161
|
+
#
|
162
|
+
# @param [Proc] block The block containing Redis commands to execute in pipeline
|
163
|
+
# @yield [Redis] conn The Redis connection configured for pipelined mode
|
164
|
+
# @return [MultiResult] Result object with success status and command results
|
165
|
+
#
|
166
|
+
# @raise [Familia::OperationModeError] When called with incompatible connection handlers
|
167
|
+
# (e.g., FiberConnectionHandler or CachedConnectionHandler that don't support pipelines)
|
168
|
+
#
|
169
|
+
# @example Basic instance pipeline
|
170
|
+
# customer = Customer.new(custid: 'cust_123')
|
171
|
+
# result = customer.pipelined do |conn|
|
172
|
+
# conn.hset(customer.dbkey, 'last_login', Time.now.to_i)
|
173
|
+
# conn.hincrby(customer.dbkey, 'login_count', 1)
|
174
|
+
# conn.sadd('recent_logins', customer.identifier)
|
175
|
+
# conn.hget(customer.dbkey, 'login_count')
|
176
|
+
# end
|
177
|
+
# result.successful? # => true
|
178
|
+
# result.results # => ["OK", 15, "OK", "15"]
|
179
|
+
# result.results.last # => "15" (new login count)
|
180
|
+
#
|
181
|
+
# @example Performance optimization for object operations
|
182
|
+
# user = User.new(userid: 'user_456')
|
183
|
+
#
|
184
|
+
# # Instead of multiple round-trips:
|
185
|
+
# # user.save # Round-trip 1
|
186
|
+
# # user.tags.add('premium') # Round-trip 2
|
187
|
+
# # user.sessions.clear # Round-trip 3
|
188
|
+
#
|
189
|
+
# # Use pipeline for single round-trip:
|
190
|
+
# result = user.pipelined do |conn|
|
191
|
+
# conn.hmset(user.dbkey, user.to_h_for_storage)
|
192
|
+
# conn.sadd(user.tags.dbkey, 'premium')
|
193
|
+
# conn.del(user.sessions.dbkey)
|
194
|
+
# end
|
195
|
+
# # All operations completed in one network round-trip
|
196
|
+
#
|
197
|
+
# @example Using with object's database context
|
198
|
+
# class Session < Familia::Horreum
|
199
|
+
# logical_database 3 # Use database 3
|
200
|
+
# field :user_id
|
201
|
+
# field :expires_at
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# session = Session.new(session_id: 'sess_789')
|
205
|
+
# result = session.pipelined do |conn|
|
206
|
+
# # Commands automatically execute in database 3
|
207
|
+
# conn.hset(session.dbkey, 'user_id', 'user_123')
|
208
|
+
# conn.hset(session.dbkey, 'expires_at', 1.hour.from_now.to_i)
|
209
|
+
# conn.expire(session.dbkey, 3600)
|
210
|
+
# end
|
211
|
+
#
|
212
|
+
# @example Reentrant behavior with global pipelines
|
213
|
+
# customer = Customer.new(custid: 'cust_abc')
|
214
|
+
#
|
215
|
+
# # When called within a global pipeline, reuses the pipeline connection
|
216
|
+
# result = Familia.pipelined do |global_conn|
|
217
|
+
# global_conn.set('global_counter', 0)
|
218
|
+
#
|
219
|
+
# # This reuses the same pipeline connection
|
220
|
+
# customer.pipelined do |local_conn|
|
221
|
+
# local_conn.hset(customer.dbkey, 'updated', Time.now.to_i)
|
222
|
+
# Redis::Future.new # Returns Redis::Future in nested context
|
223
|
+
# end
|
224
|
+
# end
|
225
|
+
#
|
226
|
+
# @note Connection Inheritance:
|
227
|
+
# - Uses object's logical_database setting if configured
|
228
|
+
# - Inherits class-level database settings
|
229
|
+
# - Falls back to instance-level dbclient if set
|
230
|
+
# - Uses global connection chain as final fallback
|
231
|
+
#
|
232
|
+
# @note Pipeline Context:
|
233
|
+
# - When called outside global pipeline: Creates local MultiResult
|
234
|
+
# - When called inside global pipeline: Yields to existing pipeline
|
235
|
+
# - Maintains proper Fiber-local state for nested calls
|
236
|
+
#
|
237
|
+
# @note Performance Considerations:
|
238
|
+
# - Best for multiple independent operations on the same object
|
239
|
+
# - Reduces network latency by batching commands
|
240
|
+
# - Commands execute independently (some may succeed, others fail)
|
241
|
+
#
|
242
|
+
# @see Familia.pipelined For global pipeline method
|
243
|
+
# @see MultiResult For details on the return value structure
|
244
|
+
# @see Familia.transaction For atomic command execution
|
245
|
+
def pipelined(&block)
|
246
|
+
Familia::Connection::PipelineCore.execute_pipeline(-> { dbclient }, &block)
|
247
|
+
end
|
248
|
+
alias pipeline pipelined
|
249
|
+
|
250
|
+
private
|
251
|
+
|
252
|
+
# Builds the class-level connection chain with handlers in priority order
|
253
|
+
def build_connection_chain
|
254
|
+
# Cache handlers at class level to avoid creating new instances per model instance
|
255
|
+
@fiber_connection_handler ||= Familia::Connection::FiberConnectionHandler.new
|
256
|
+
@provider_connection_handler ||= Familia::Connection::ProviderConnectionHandler.new
|
257
|
+
|
258
|
+
# Determine the appropriate class context
|
259
|
+
# When called from instance: self is instance, self.class is the model class
|
260
|
+
# When called from class: self is the model class
|
261
|
+
klass = self.is_a?(Class) ? self : self.class
|
262
|
+
|
263
|
+
# Always check class first for @dbclient since instance-level connections were removed
|
264
|
+
@cached_connection_handler ||= Familia::Connection::CachedConnectionHandler.new(klass)
|
265
|
+
@create_connection_handler ||= Familia::Connection::CreateConnectionHandler.new(klass)
|
266
|
+
|
267
|
+
Familia::Connection::ResponsibilityChain.new
|
268
|
+
.add_handler(Familia::Connection::FiberTransactionHandler.instance)
|
269
|
+
.add_handler(@fiber_connection_handler)
|
270
|
+
.add_handler(@provider_connection_handler)
|
271
|
+
.add_handler(@cached_connection_handler)
|
272
|
+
.add_handler(@create_connection_handler)
|
70
273
|
end
|
71
274
|
end
|
72
275
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# lib/familia/horreum/database_commands.rb
|
2
2
|
|
3
3
|
module Familia
|
4
|
-
#
|
4
|
+
# Familia::Horreum
|
5
5
|
#
|
6
6
|
# This module is included in classes that include Familia, providing
|
7
7
|
# instance-level functionality for Database operations and object management.
|
8
8
|
#
|
9
9
|
class Horreum
|
10
|
-
#
|
10
|
+
# DatabaseCommands - Instance-level methods for horreum models that call Database commands
|
11
11
|
#
|
12
12
|
# NOTE: There is no hgetall for Horreum. This is because Horreum
|
13
13
|
# is a single hash in Database that we aren't meant to have be working
|
@@ -20,11 +20,11 @@ module Familia
|
|
20
20
|
dbclient.move dbkey, logical_database
|
21
21
|
end
|
22
22
|
|
23
|
-
# Checks if the calling object's key exists in
|
23
|
+
# Checks if the calling object's key exists in the database.
|
24
24
|
#
|
25
25
|
# @param check_size [Boolean] When true (default), also verifies the hash has a non-zero size.
|
26
26
|
# When false, only checks key existence regardless of content.
|
27
|
-
# @return [Boolean] Returns `true` if the key exists in
|
27
|
+
# @return [Boolean] Returns `true` if the key exists in the database. When `check_size` is true,
|
28
28
|
# also requires the hash to have at least one field.
|
29
29
|
#
|
30
30
|
# @example Check existence with size validation (default behavior)
|
@@ -49,6 +49,7 @@ module Familia
|
|
49
49
|
dbclient.hlen dbkey
|
50
50
|
end
|
51
51
|
alias size field_count
|
52
|
+
alias length field_count
|
52
53
|
|
53
54
|
# Sets a timeout on key. After the timeout has expired, the key will
|
54
55
|
# automatically be deleted. Returns 1 if the timeout was set, 0 if key
|
@@ -56,19 +57,19 @@ module Familia
|
|
56
57
|
#
|
57
58
|
def expire(default_expiration = nil)
|
58
59
|
default_expiration ||= self.class.default_expiration
|
59
|
-
Familia.trace :EXPIRE,
|
60
|
+
Familia.trace :EXPIRE, nil, default_expiration if Familia.debug?
|
60
61
|
dbclient.expire dbkey, default_expiration.to_i
|
61
62
|
end
|
62
63
|
|
63
64
|
# Retrieves the remaining time to live (TTL) for the object's dbkey.
|
64
65
|
#
|
65
|
-
# This method accesses the
|
66
|
+
# This method accesses the objects Database client to obtain the TTL of `dbkey`.
|
66
67
|
# If debugging is enabled, it logs the TTL retrieval operation using `Familia.trace`.
|
67
68
|
#
|
68
69
|
# @return [Integer] The TTL of the key in seconds. Returns -1 if the key does not exist
|
69
70
|
# or has no associated expire time.
|
70
71
|
def current_expiration
|
71
|
-
Familia.trace :CURRENT_EXPIRATION,
|
72
|
+
Familia.trace :CURRENT_EXPIRATION, nil, uri if Familia.debug?
|
72
73
|
dbclient.ttl dbkey
|
73
74
|
end
|
74
75
|
|
@@ -77,32 +78,32 @@ module Familia
|
|
77
78
|
# @param field [String] The field to remove from the hash.
|
78
79
|
# @return [Integer] The number of fields that were removed from the hash (0 or 1).
|
79
80
|
def remove_field(field)
|
80
|
-
Familia.trace :HDEL,
|
81
|
+
Familia.trace :HDEL, nil, field if Familia.debug?
|
81
82
|
dbclient.hdel dbkey, field
|
82
83
|
end
|
83
84
|
alias remove remove_field # deprecated
|
84
85
|
|
85
86
|
def data_type
|
86
|
-
Familia.trace :DATATYPE,
|
87
|
+
Familia.trace :DATATYPE, nil, uri if Familia.debug?
|
87
88
|
dbclient.type dbkey(suffix)
|
88
89
|
end
|
89
90
|
|
90
91
|
# For parity with DataType#hgetall
|
91
92
|
def hgetall
|
92
|
-
Familia.trace :HGETALL,
|
93
|
+
Familia.trace :HGETALL, nil, uri if Familia.debug?
|
93
94
|
dbclient.hgetall dbkey(suffix)
|
94
95
|
end
|
95
96
|
alias all hgetall
|
96
97
|
|
97
98
|
def hget(field)
|
98
|
-
Familia.trace :HGET,
|
99
|
+
Familia.trace :HGET, nil, field if Familia.debug?
|
99
100
|
dbclient.hget dbkey(suffix), field
|
100
101
|
end
|
101
102
|
|
102
103
|
# @return The number of fields that were added to the hash. If the
|
103
104
|
# field already exists, this will return 0.
|
104
105
|
def hset(field, value)
|
105
|
-
Familia.trace :HSET,
|
106
|
+
Familia.trace :HSET, nil, field if Familia.debug?
|
106
107
|
dbclient.hset dbkey, field, value
|
107
108
|
end
|
108
109
|
|
@@ -115,18 +116,18 @@ module Familia
|
|
115
116
|
# @return [Integer] 1 if the field is a new field in the hash and the value was set,
|
116
117
|
# 0 if the field already exists in the hash and no operation was performed
|
117
118
|
def hsetnx(field, value)
|
118
|
-
Familia.trace :HSETNX,
|
119
|
+
Familia.trace :HSETNX, nil, field if Familia.debug?
|
119
120
|
dbclient.hsetnx dbkey, field, value
|
120
121
|
end
|
121
122
|
|
122
123
|
def hmset(hsh = {})
|
123
124
|
hsh ||= to_h
|
124
|
-
Familia.trace :HMSET,
|
125
|
+
Familia.trace :HMSET, nil, hsh if Familia.debug?
|
125
126
|
dbclient.hmset dbkey(suffix), hsh
|
126
127
|
end
|
127
128
|
|
128
129
|
def hkeys
|
129
|
-
Familia.trace :HKEYS,
|
130
|
+
Familia.trace :HKEYS, nil, 'uri' if Familia.debug?
|
130
131
|
dbclient.hkeys dbkey(suffix)
|
131
132
|
end
|
132
133
|
|
@@ -169,14 +170,23 @@ module Familia
|
|
169
170
|
end
|
170
171
|
alias has_key? key?
|
171
172
|
|
172
|
-
# Deletes the
|
173
|
+
# Deletes the dbkey for this horreum :object.
|
174
|
+
#
|
175
|
+
# It does not delete the related fields keys. See destroy!
|
176
|
+
#
|
173
177
|
# @return [Boolean] true if the key was deleted, false otherwise
|
174
178
|
def delete!
|
175
|
-
Familia.trace :DELETE!,
|
179
|
+
Familia.trace :DELETE!, nil, uri if Familia.debug?
|
180
|
+
|
181
|
+
# Delete the main object key
|
176
182
|
ret = dbclient.del dbkey
|
177
183
|
ret.positive?
|
178
184
|
end
|
179
185
|
alias clear delete!
|
186
|
+
|
187
|
+
def echo(*args)
|
188
|
+
dbclient.echo "[#{self.class}] #{args.join(' ')}"
|
189
|
+
end
|
180
190
|
end
|
181
191
|
end
|
182
192
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
# lib/familia/horreum/serialization.rb
|
2
|
-
|
2
|
+
|
3
3
|
module Familia
|
4
4
|
# Familia::Horreum
|
5
5
|
#
|
@@ -21,7 +21,7 @@ module Familia
|
|
21
21
|
# - nil - Valid response for certain operations
|
22
22
|
#
|
23
23
|
# @example Validating a command response
|
24
|
-
# response =
|
24
|
+
# response = dbclient.set("key", "value")
|
25
25
|
# valid = @valid_command_return_values.include?(response)
|
26
26
|
# # => true if response is "OK"
|
27
27
|
#
|
@@ -31,7 +31,7 @@ module Familia
|
|
31
31
|
attr_reader :valid_command_return_values
|
32
32
|
end
|
33
33
|
|
34
|
-
# Serialization
|
34
|
+
# Serialization - Instance-level methods for object persistence and retrieval
|
35
35
|
# Handles conversion between Ruby objects and Valkey hash storage
|
36
36
|
#
|
37
37
|
module Serialization
|
@@ -61,7 +61,7 @@ module Familia
|
|
61
61
|
# @see #commit_fields The underlying method that performs the field persistence
|
62
62
|
#
|
63
63
|
def save(update_expiration: true)
|
64
|
-
Familia.trace :SAVE,
|
64
|
+
Familia.trace :SAVE, nil, uri if Familia.debug?
|
65
65
|
|
66
66
|
# No longer need to sync computed identifier with a cache field
|
67
67
|
self.created ||= Familia.now.to_i if respond_to?(:created)
|
@@ -71,6 +71,9 @@ module Familia
|
|
71
71
|
#
|
72
72
|
ret = commit_fields(update_expiration: update_expiration)
|
73
73
|
|
74
|
+
# Add to class-level instances collection after successful save
|
75
|
+
self.class.instances.add(identifier, Familia.now) if ret && self.class.respond_to?(:instances)
|
76
|
+
|
74
77
|
Familia.ld "[save] #{self.class} #{dbkey} #{ret} (update_expiration: #{update_expiration})"
|
75
78
|
|
76
79
|
# Did Database accept our offering?
|
@@ -123,7 +126,7 @@ module Familia
|
|
123
126
|
identifier_field = self.class.identifier_field
|
124
127
|
|
125
128
|
Familia.ld "[save_if_not_exists]: #{self.class} #{identifier_field}=#{identifier}"
|
126
|
-
Familia.trace :SAVE_IF_NOT_EXISTS,
|
129
|
+
Familia.trace :SAVE_IF_NOT_EXISTS, nil, uri if Familia.debug?
|
127
130
|
|
128
131
|
dbclient.watch(dbkey) do
|
129
132
|
if dbclient.exists(dbkey).positive?
|
@@ -180,7 +183,7 @@ module Familia
|
|
180
183
|
|
181
184
|
# Updates multiple fields atomically in a Database transaction.
|
182
185
|
#
|
183
|
-
# @param
|
186
|
+
# @param kwargs [Hash] Field names and values to update. Special key :update_expiration
|
184
187
|
# controls whether to update key expiration (default: true)
|
185
188
|
# @return [MultiResult] Transaction result
|
186
189
|
#
|
@@ -194,9 +197,9 @@ module Familia
|
|
194
197
|
update_expiration = kwargs.delete(:update_expiration) { true }
|
195
198
|
fields = kwargs
|
196
199
|
|
197
|
-
Familia.trace :BATCH_UPDATE,
|
200
|
+
Familia.trace :BATCH_UPDATE, nil, fields.keys if Familia.debug?
|
198
201
|
|
199
|
-
|
202
|
+
transaction_result = transaction do |conn|
|
200
203
|
fields.each do |field, value|
|
201
204
|
prepared_value = serialize_value(value)
|
202
205
|
conn.hset dbkey, field, prepared_value
|
@@ -208,9 +211,8 @@ module Familia
|
|
208
211
|
# Update expiration if requested and supported
|
209
212
|
self.update_expiration(default_expiration: nil) if update_expiration && respond_to?(:update_expiration)
|
210
213
|
|
211
|
-
# Return
|
212
|
-
|
213
|
-
MultiResult.new(summary_boolean, command_return_values)
|
214
|
+
# Return the MultiResult directly (transaction already returns MultiResult)
|
215
|
+
transaction_result
|
214
216
|
end
|
215
217
|
|
216
218
|
# Updates the object by applying multiple field values.
|
@@ -235,11 +237,12 @@ module Familia
|
|
235
237
|
self
|
236
238
|
end
|
237
239
|
|
238
|
-
# Permanently removes this object from the DB
|
240
|
+
# Permanently removes this object and its related fields from the DB.
|
239
241
|
#
|
240
|
-
# Deletes the object's
|
242
|
+
# Deletes the object's database key and all associated data. This operation
|
241
243
|
# is irreversible and will permanently destroy all stored information
|
242
|
-
# for this object instance
|
244
|
+
# for this object instance and the additional list, set, hash, string
|
245
|
+
# etc fields defined for this class.
|
243
246
|
#
|
244
247
|
# @return [void]
|
245
248
|
#
|
@@ -259,8 +262,25 @@ module Familia
|
|
259
262
|
# @see #delete! The underlying method that performs the key deletion
|
260
263
|
#
|
261
264
|
def destroy!
|
262
|
-
Familia.trace :DESTROY,
|
263
|
-
|
265
|
+
Familia.trace :DESTROY, dbkey, uri
|
266
|
+
|
267
|
+
# Execute all deletion operations within a transaction
|
268
|
+
transaction do |conn|
|
269
|
+
# Delete the main object key
|
270
|
+
conn.del(dbkey)
|
271
|
+
|
272
|
+
# Delete all related fields if present
|
273
|
+
if self.class.relations?
|
274
|
+
Familia.trace :DELETE_RELATED_FIELDS!, nil,
|
275
|
+
"#{self.class} has relations: #{self.class.related_fields.keys}"
|
276
|
+
|
277
|
+
self.class.related_fields.each do |name, _definition|
|
278
|
+
obj = send(name)
|
279
|
+
Familia.trace :DELETE_RELATED_FIELD, name, "Deleting related field #{name} (#{obj.dbkey})"
|
280
|
+
conn.del(obj.dbkey)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
264
284
|
end
|
265
285
|
|
266
286
|
# Clears all fields by setting them to nil.
|
@@ -306,7 +326,7 @@ module Familia
|
|
306
326
|
# no authoritative source in Valkey storage.
|
307
327
|
#
|
308
328
|
def refresh!
|
309
|
-
Familia.trace :REFRESH,
|
329
|
+
Familia.trace :REFRESH, nil, uri if Familia.debug?
|
310
330
|
raise Familia::KeyNotFoundError, dbkey unless dbclient.exists(dbkey)
|
311
331
|
|
312
332
|
fields = hgetall
|
@@ -442,7 +462,7 @@ module Familia
|
|
442
462
|
#
|
443
463
|
# The serialization process:
|
444
464
|
# 1. Attempts conversion using Familia.distinguisher with relaxed type checking
|
445
|
-
# 2. For Hash/Array types that return nil, tries custom dump_method or
|
465
|
+
# 2. For Hash/Array types that return nil, tries custom dump_method or Familia::JsonSerializer.dump
|
446
466
|
# 3. Logs warnings when serialization fails completely
|
447
467
|
#
|
448
468
|
# @param val [Object] The Ruby object to serialize for Valkey storage
|
@@ -485,7 +505,7 @@ module Familia
|
|
485
505
|
# Hash or Array types. Simple string values are returned as-is.
|
486
506
|
#
|
487
507
|
# @param val [String] The string value from Database to deserialize
|
488
|
-
# @param
|
508
|
+
# @param symbolize [Boolean] Whether to symbolize hash keys (default: true for compatibility)
|
489
509
|
# @return [Object] The deserialized value (Hash, Array, or original string)
|
490
510
|
#
|
491
511
|
def deserialize_value(val, symbolize: true)
|
@@ -522,7 +542,7 @@ module Familia
|
|
522
542
|
field_type = self.class.field_types[field_name]
|
523
543
|
next unless field_type&.method_name
|
524
544
|
|
525
|
-
#
|
545
|
+
# UnsortedSet the transient field back to nil
|
526
546
|
send("#{field_type.method_name}=", nil)
|
527
547
|
Familia.ld "[reset_transient_fields!] Reset #{field_name} to nil"
|
528
548
|
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
|
-
# 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
|
@@ -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
|