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,297 @@
|
|
1
|
+
# lib/familia/horreum/connection.rb
|
2
|
+
|
3
|
+
module Familia
|
4
|
+
class Horreum
|
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)
|
9
|
+
module Connection
|
10
|
+
attr_reader :uri
|
11
|
+
|
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.
|
45
|
+
#
|
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)
|
50
|
+
#
|
51
|
+
# @return [Redis] the Database connection instance.
|
52
|
+
#
|
53
|
+
def dbclient(uri = nil)
|
54
|
+
@class_connection_chain ||= build_connection_chain
|
55
|
+
@class_connection_chain.handle(uri)
|
56
|
+
end
|
57
|
+
|
58
|
+
def connect(*)
|
59
|
+
create_dbclient(*)
|
60
|
+
end
|
61
|
+
|
62
|
+
def uri=(uri)
|
63
|
+
@uri = normalize_uri(uri)
|
64
|
+
end
|
65
|
+
alias url uri
|
66
|
+
alias url= uri=
|
67
|
+
|
68
|
+
# Perform a sacred Database transaction ritual.
|
69
|
+
#
|
70
|
+
# This method creates a protective circle around your Database operations,
|
71
|
+
# ensuring they all succeed or fail together. It's like a group hug for your
|
72
|
+
# data operations, but with more ACID properties.
|
73
|
+
#
|
74
|
+
# @yield [conn] A block where you can perform your Database incantations.
|
75
|
+
# @yieldparam conn [Redis] A Database connection in multi mode.
|
76
|
+
#
|
77
|
+
# @example Performing a Database rain dance
|
78
|
+
# transaction do |conn|
|
79
|
+
# conn.set("weather", "rainy")
|
80
|
+
# conn.set("mood", "melancholic")
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# @note This method works with the global Familia.transaction context when available
|
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
|
151
|
+
def transaction(&)
|
152
|
+
ensure_relatives_initialized!
|
153
|
+
Familia::Connection::TransactionCore.execute_transaction(-> { dbclient }, &)
|
154
|
+
end
|
155
|
+
alias multi transaction
|
156
|
+
|
157
|
+
# Executes Redis commands in a pipeline using this object's connection context.
|
158
|
+
#
|
159
|
+
# Batches multiple Redis commands together and sends them in a single network
|
160
|
+
# round-trip for improved performance. Uses the object's database and connection
|
161
|
+
# settings. Returns a MultiResult object for consistency with global methods.
|
162
|
+
#
|
163
|
+
# @param [Proc] block The block containing Redis commands to execute in pipeline
|
164
|
+
# @yield [Redis] conn The Redis connection configured for pipelined mode
|
165
|
+
# @return [MultiResult] Result object with success status and command results
|
166
|
+
#
|
167
|
+
# @raise [Familia::OperationModeError] When called with incompatible connection handlers
|
168
|
+
# (e.g., FiberConnectionHandler or CachedConnectionHandler that don't support pipelines)
|
169
|
+
#
|
170
|
+
# @example Basic instance pipeline
|
171
|
+
# customer = Customer.new(custid: 'cust_123')
|
172
|
+
# result = customer.pipelined do |conn|
|
173
|
+
# conn.hset(customer.dbkey, 'last_login', Time.now.to_i)
|
174
|
+
# conn.hincrby(customer.dbkey, 'login_count', 1)
|
175
|
+
# conn.sadd('recent_logins', customer.identifier)
|
176
|
+
# conn.hget(customer.dbkey, 'login_count')
|
177
|
+
# end
|
178
|
+
# result.successful? # => true
|
179
|
+
# result.results # => ["OK", 15, "OK", "15"]
|
180
|
+
# result.results.last # => "15" (new login count)
|
181
|
+
#
|
182
|
+
# @example Performance optimization for object operations
|
183
|
+
# user = User.new(userid: 'user_456')
|
184
|
+
#
|
185
|
+
# # Instead of multiple round-trips:
|
186
|
+
# # user.save # Round-trip 1
|
187
|
+
# # user.tags.add('premium') # Round-trip 2
|
188
|
+
# # user.sessions.clear # Round-trip 3
|
189
|
+
#
|
190
|
+
# # Use pipeline for single round-trip:
|
191
|
+
# result = user.pipelined do |conn|
|
192
|
+
# conn.hmset(user.dbkey, user.to_h_for_storage)
|
193
|
+
# conn.sadd(user.tags.dbkey, 'premium')
|
194
|
+
# conn.del(user.sessions.dbkey)
|
195
|
+
# end
|
196
|
+
# # All operations completed in one network round-trip
|
197
|
+
#
|
198
|
+
# @example Using with object's database context
|
199
|
+
# class Session < Familia::Horreum
|
200
|
+
# logical_database 3 # Use database 3
|
201
|
+
# field :user_id
|
202
|
+
# field :expires_at
|
203
|
+
# end
|
204
|
+
#
|
205
|
+
# session = Session.new(session_id: 'sess_789')
|
206
|
+
# result = session.pipelined do |conn|
|
207
|
+
# # Commands automatically execute in database 3
|
208
|
+
# conn.hset(session.dbkey, 'user_id', 'user_123')
|
209
|
+
# conn.hset(session.dbkey, 'expires_at', 1.hour.from_now.to_i)
|
210
|
+
# conn.expire(session.dbkey, 3600)
|
211
|
+
# end
|
212
|
+
#
|
213
|
+
# @example Reentrant behavior with global pipelines
|
214
|
+
# customer = Customer.new(custid: 'cust_abc')
|
215
|
+
#
|
216
|
+
# # When called within a global pipeline, reuses the pipeline connection
|
217
|
+
# result = Familia.pipelined do |global_conn|
|
218
|
+
# global_conn.set('global_counter', 0)
|
219
|
+
#
|
220
|
+
# # This reuses the same pipeline connection
|
221
|
+
# customer.pipelined do |local_conn|
|
222
|
+
# local_conn.hset(customer.dbkey, 'updated', Time.now.to_i)
|
223
|
+
# Redis::Future.new # Returns Redis::Future in nested context
|
224
|
+
# end
|
225
|
+
# end
|
226
|
+
#
|
227
|
+
# @note Connection Inheritance:
|
228
|
+
# - Uses object's logical_database setting if configured
|
229
|
+
# - Inherits class-level database settings
|
230
|
+
# - Falls back to instance-level dbclient if set
|
231
|
+
# - Uses global connection chain as final fallback
|
232
|
+
#
|
233
|
+
# @note Pipeline Context:
|
234
|
+
# - When called outside global pipeline: Creates local MultiResult
|
235
|
+
# - When called inside global pipeline: Yields to existing pipeline
|
236
|
+
# - Maintains proper Fiber-local state for nested calls
|
237
|
+
#
|
238
|
+
# @note Performance Considerations:
|
239
|
+
# - Best for multiple independent operations on the same object
|
240
|
+
# - Reduces network latency by batching commands
|
241
|
+
# - Commands execute independently (some may succeed, others fail)
|
242
|
+
#
|
243
|
+
# @see Familia.pipelined For global pipeline method
|
244
|
+
# @see MultiResult For details on the return value structure
|
245
|
+
# @see Familia.transaction For atomic command execution
|
246
|
+
def pipelined(&block)
|
247
|
+
ensure_relatives_initialized!
|
248
|
+
Familia::Connection::PipelineCore.execute_pipeline(-> { dbclient }, &block)
|
249
|
+
end
|
250
|
+
alias pipeline pipelined
|
251
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
# Ensures that related fields have been initialized before entering transactions or pipelines.
|
255
|
+
#
|
256
|
+
# This prevents Redis::Future errors when lazy initialization would occur inside
|
257
|
+
# transaction/pipeline blocks. When commands execute inside transactions, Redis returns
|
258
|
+
# Future objects that don't respond to standard methods, causing cryptic NoMethodError.
|
259
|
+
#
|
260
|
+
# @raise [RuntimeError] if instance has relations but they haven't been initialized
|
261
|
+
# @note Skips check for class methods - they create temporary instances internally
|
262
|
+
# @note Uses singleton class to avoid polluting instance variables
|
263
|
+
def ensure_relatives_initialized!
|
264
|
+
return if is_a?(Class) # Class methods handle their own instances
|
265
|
+
return unless self.class.respond_to?(:relations?) && self.class.relations?
|
266
|
+
return if singleton_class.instance_variable_defined?(:"@relatives_initialized")
|
267
|
+
|
268
|
+
raise "#{self.class} has related fields but they haven't been initialized. " \
|
269
|
+
"Did you override initialize without calling super? " \
|
270
|
+
"Related fields: #{self.class.related_fields.keys.join(', ')}"
|
271
|
+
end
|
272
|
+
|
273
|
+
# Builds the class-level connection chain with handlers in priority order
|
274
|
+
def build_connection_chain
|
275
|
+
# Cache handlers at class level to avoid creating new instances per model instance
|
276
|
+
@fiber_connection_handler ||= Familia::Connection::FiberConnectionHandler.new
|
277
|
+
@provider_connection_handler ||= Familia::Connection::ProviderConnectionHandler.new
|
278
|
+
|
279
|
+
# Determine the appropriate class context
|
280
|
+
# When called from instance: self is instance, self.class is the model class
|
281
|
+
# When called from class: self is the model class
|
282
|
+
klass = self.is_a?(Class) ? self : self.class
|
283
|
+
|
284
|
+
# Always check class first for @dbclient since instance-level connections were removed
|
285
|
+
@cached_connection_handler ||= Familia::Connection::CachedConnectionHandler.new(klass)
|
286
|
+
@create_connection_handler ||= Familia::Connection::CreateConnectionHandler.new(klass)
|
287
|
+
|
288
|
+
Familia::Connection::ResponsibilityChain.new
|
289
|
+
.add_handler(Familia::Connection::FiberTransactionHandler.instance)
|
290
|
+
.add_handler(@fiber_connection_handler)
|
291
|
+
.add_handler(@provider_connection_handler)
|
292
|
+
.add_handler(@cached_connection_handler)
|
293
|
+
.add_handler(@create_connection_handler)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
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
|