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
@@ -1,7 +1,6 @@
|
|
1
|
-
# lib/familia/horreum/
|
1
|
+
# lib/familia/horreum/definition.rb
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
require_relative '../shared/settings'
|
3
|
+
require_relative 'settings'
|
5
4
|
|
6
5
|
module Familia
|
7
6
|
VALID_STRATEGIES = %i[raise skip ignore warn overwrite].freeze
|
@@ -32,21 +31,103 @@ module Familia
|
|
32
31
|
@dump_method = nil
|
33
32
|
@load_method = nil
|
34
33
|
|
35
|
-
#
|
34
|
+
# Field groups
|
35
|
+
@field_groups = nil
|
36
|
+
@current_field_group = nil
|
37
|
+
|
38
|
+
# DefinitionMethods - Class-level DSL methods for defining Horreum model structure
|
36
39
|
#
|
37
40
|
# This module is extended into classes that include Familia::Horreum,
|
38
|
-
# providing methods for
|
41
|
+
# providing class methods for defining model structure and configuration
|
42
|
+
# (e.g., Customer.field :name, Customer.identifier_field :custid).
|
39
43
|
#
|
40
44
|
# Key features:
|
41
|
-
# *
|
42
|
-
# *
|
43
|
-
# * Provides
|
45
|
+
# * Defines DSL methods for field definitions (field, identifier_field)
|
46
|
+
# * Includes RelatedFieldsManagement for DataType field DSL (list, set, zset, etc.)
|
47
|
+
# * Provides class-level configuration (prefix, suffix, logical_database)
|
48
|
+
# * Manages field metadata and inheritance
|
44
49
|
#
|
45
50
|
module DefinitionMethods
|
46
51
|
include Familia::Settings
|
47
52
|
include Familia::Horreum::RelatedFieldsManagement # Provides DataType field methods
|
48
53
|
|
49
|
-
|
54
|
+
# Defines a field group to organize related fields.
|
55
|
+
#
|
56
|
+
# Field groups provide a way to categorize and query fields by purpose or feature.
|
57
|
+
# When a block is provided, fields defined within the block are automatically
|
58
|
+
# added to the group. Without a block, an empty group is initialized.
|
59
|
+
#
|
60
|
+
# @param name [Symbol, String] the name of the field group
|
61
|
+
# @yield optional block for defining fields within the group
|
62
|
+
# @return [Array<Symbol>] the array of field names in the group
|
63
|
+
#
|
64
|
+
# @raise [Familia::Problem] if attempting to nest field groups
|
65
|
+
#
|
66
|
+
# @example Manual field grouping
|
67
|
+
# class User < Familia::Horreum
|
68
|
+
# field_group :personal_info do
|
69
|
+
# field :name
|
70
|
+
# field :email
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# User.personal_info # => [:name, :email]
|
75
|
+
#
|
76
|
+
# @example Initialize empty group
|
77
|
+
# class User < Familia::Horreum
|
78
|
+
# field_group :placeholder
|
79
|
+
# end
|
80
|
+
#
|
81
|
+
# User.placeholder # => []
|
82
|
+
#
|
83
|
+
def field_group(name, &block)
|
84
|
+
|
85
|
+
# Prevent nested field groups
|
86
|
+
if @current_field_group
|
87
|
+
raise Familia::Problem,
|
88
|
+
"Cannot define field group :#{name} while :#{@current_field_group} is being defined. " \
|
89
|
+
"Nested field groups are not supported."
|
90
|
+
end
|
91
|
+
|
92
|
+
# Initialize group
|
93
|
+
field_groups[name.to_sym] ||= []
|
94
|
+
|
95
|
+
if block_given?
|
96
|
+
@current_field_group = name.to_sym
|
97
|
+
begin
|
98
|
+
instance_eval(&block)
|
99
|
+
ensure
|
100
|
+
@current_field_group = nil
|
101
|
+
end
|
102
|
+
else
|
103
|
+
Familia.ld "[field_group] Created field group :#{name} but no block given" if Familia.debug?
|
104
|
+
end
|
105
|
+
|
106
|
+
field_groups[name.to_sym]
|
107
|
+
end
|
108
|
+
|
109
|
+
# Returns the list of all field group names defined for the class.
|
110
|
+
#
|
111
|
+
# @return [Array<Symbol>] array of field group names
|
112
|
+
#
|
113
|
+
# @example
|
114
|
+
# class User < Familia::Horreum
|
115
|
+
# field_group :personal_info do
|
116
|
+
# field :name
|
117
|
+
# end
|
118
|
+
# field_group :metadata do
|
119
|
+
# field :created_at
|
120
|
+
# end
|
121
|
+
# end
|
122
|
+
#
|
123
|
+
# User.field_groups # => [
|
124
|
+
# :personal_info => [...],
|
125
|
+
# :metadata => [..]
|
126
|
+
# ]
|
127
|
+
#
|
128
|
+
def field_groups
|
129
|
+
@field_groups ||= {}
|
130
|
+
end
|
50
131
|
|
51
132
|
# Sets or retrieves the unique identifier field for the class.
|
52
133
|
#
|
@@ -77,13 +158,13 @@ module Familia
|
|
77
158
|
#
|
78
159
|
# This method defines a new field for the class, creating getter and setter
|
79
160
|
# instance methods similar to `attr_accessor`. It also generates a fast
|
80
|
-
# writer method for immediate persistence to
|
161
|
+
# writer method for immediate persistence to the database.
|
81
162
|
#
|
82
163
|
# @param name [Symbol, String] the name of the field to define. If a method
|
83
164
|
# with the same name already exists, an error is raised.
|
84
165
|
# @param as [Symbol, String, false, nil] as the name to use for the accessor method (defaults to name).
|
85
166
|
# If false or nil, no accessor methods are created.
|
86
|
-
# @param fast_method [Symbol, false, nil] the name to use for the fast writer method (defaults to
|
167
|
+
# @param fast_method [Symbol, false, nil] the name to use for the fast writer method (defaults to :`"#{name}!"`).
|
87
168
|
# If false or nil, no fast writer method is created.
|
88
169
|
# @param on_conflict [Symbol] conflict resolution strategy when method already exists:
|
89
170
|
# - :raise - raise error if method exists (default)
|
@@ -98,37 +179,37 @@ module Familia
|
|
98
179
|
#
|
99
180
|
def field(name, as: name, fast_method: :"#{name}!", on_conflict: :raise, category: nil)
|
100
181
|
# Use field type system for consistency
|
101
|
-
require_relative '
|
182
|
+
require_relative '../field_type'
|
102
183
|
|
103
184
|
# Create appropriate field type based on category
|
104
185
|
field_type = if category == :transient
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
186
|
+
require_relative '../features/transient_fields/transient_field_type'
|
187
|
+
TransientFieldType.new(name, as: as, fast_method: false, on_conflict: on_conflict)
|
188
|
+
else
|
189
|
+
# For regular fields and other categories, create custom field type with category override
|
190
|
+
custom_field_type = Class.new(FieldType) do
|
191
|
+
define_method :category do
|
192
|
+
category || :field
|
193
|
+
end
|
194
|
+
end
|
195
|
+
custom_field_type.new(name, as: as, fast_method: fast_method, on_conflict: on_conflict)
|
196
|
+
end
|
116
197
|
|
117
198
|
register_field_type(field_type)
|
118
199
|
end
|
119
200
|
|
120
|
-
# Sets or retrieves the suffix for generating Redis keys.
|
201
|
+
# Sets or retrieves the suffix for generating Valkey/Redis keys.
|
121
202
|
#
|
122
203
|
# @param a [String, Symbol, nil] the suffix to set (optional).
|
123
204
|
# @param blk [Proc] a block that returns the suffix (optional).
|
124
205
|
# @return [String, Symbol] the current suffix or Familia.default_suffix if none is set.
|
125
206
|
#
|
126
|
-
def suffix(
|
127
|
-
@suffix =
|
207
|
+
def suffix(val = nil, &blk)
|
208
|
+
@suffix = val || blk if val || !blk.nil?
|
128
209
|
@suffix || Familia.default_suffix
|
129
210
|
end
|
130
211
|
|
131
|
-
# Sets or retrieves the prefix for generating Redis keys.
|
212
|
+
# Sets or retrieves the prefix for generating Valkey/Redis keys.
|
132
213
|
#
|
133
214
|
# @param a [String, Symbol, nil] the prefix to set (optional).
|
134
215
|
# @return [String, Symbol] the current prefix.
|
@@ -137,20 +218,20 @@ module Familia
|
|
137
218
|
# which typically occurs with anonymous classes that haven't had their prefix
|
138
219
|
# explicitly set.
|
139
220
|
#
|
140
|
-
def prefix(
|
141
|
-
@prefix =
|
221
|
+
def prefix(val = nil)
|
222
|
+
@prefix = val if val
|
142
223
|
@prefix || begin
|
143
224
|
if name.nil?
|
144
225
|
raise Problem, 'Cannot generate prefix for anonymous class. ' \
|
145
226
|
'Use `prefix` method to set explicitly.'
|
146
227
|
end
|
147
|
-
|
228
|
+
config_name.to_sym
|
148
229
|
end
|
149
230
|
end
|
150
231
|
|
151
|
-
def logical_database(
|
152
|
-
Familia.trace :
|
153
|
-
@logical_database =
|
232
|
+
def logical_database(num = nil)
|
233
|
+
Familia.trace :LOGICAL_DATABASE_DEF, "instvar:#{@logical_database}", num if Familia.debug?
|
234
|
+
@logical_database = num unless num.nil?
|
154
235
|
@logical_database || parent&.logical_database
|
155
236
|
end
|
156
237
|
|
@@ -172,20 +253,7 @@ module Familia
|
|
172
253
|
end
|
173
254
|
|
174
255
|
def relations?
|
175
|
-
@
|
176
|
-
end
|
177
|
-
|
178
|
-
# Converts the class name into a string that can be used to look up
|
179
|
-
# configuration values. This is particularly useful when mapping
|
180
|
-
# familia models with specific database numbers in the configuration.
|
181
|
-
#
|
182
|
-
# Familia::Horreum::DefinitionMethods#config_name
|
183
|
-
#
|
184
|
-
# @example V2::Session.config_name => 'session'
|
185
|
-
#
|
186
|
-
# @return [String] The underscored class name as a string
|
187
|
-
def config_name
|
188
|
-
name.snake_case
|
256
|
+
@has_related_fields ||= false
|
189
257
|
end
|
190
258
|
|
191
259
|
def dump_method
|
@@ -234,6 +302,14 @@ module Familia
|
|
234
302
|
# Complete the registration after installation. If we do this beforehand
|
235
303
|
# we can run into issues where it looks like it's already installed.
|
236
304
|
field_types[field_type.name] = field_type
|
305
|
+
|
306
|
+
# Add to current field group if one is active
|
307
|
+
if @current_field_group
|
308
|
+
@field_groups[@current_field_group] << field_type.name
|
309
|
+
end
|
310
|
+
|
311
|
+
# Freeze the field_type to ensure immutability (maintains Data class heritage)
|
312
|
+
field_type.freeze
|
237
313
|
end
|
238
314
|
|
239
315
|
# Retrieves feature options for the current class.
|
@@ -303,26 +379,15 @@ module Familia
|
|
303
379
|
# end
|
304
380
|
#
|
305
381
|
def add_feature_options(feature_name, **options)
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
# Only set defaults for options that don't already exist
|
310
|
-
options.each do |key, value|
|
311
|
-
@feature_options[feature_name.to_sym][key] ||= value
|
312
|
-
end
|
382
|
+
@feature_options ||= {}
|
383
|
+
@feature_options[feature_name.to_sym] ||= {}
|
313
384
|
|
314
|
-
|
315
|
-
|
385
|
+
# Only set defaults for options that don't already exist
|
386
|
+
options.each do |key, value|
|
387
|
+
@feature_options[feature_name.to_sym][key] ||= value
|
388
|
+
end
|
316
389
|
|
317
|
-
|
318
|
-
#
|
319
|
-
# @param name [Symbol] The field name
|
320
|
-
# @param options [Hash] Field options
|
321
|
-
#
|
322
|
-
def transient_field(name, **)
|
323
|
-
require_relative '../../features/transient_fields/transient_field_type'
|
324
|
-
field_type = TransientFieldType.new(name, **, fast_method: false)
|
325
|
-
register_field_type(field_type)
|
390
|
+
@feature_options[feature_name.to_sym]
|
326
391
|
end
|
327
392
|
|
328
393
|
private
|
@@ -379,8 +444,8 @@ end
|
|
379
444
|
# @raise [ArgumentError] if fast_method_name doesn't end with '!'
|
380
445
|
#
|
381
446
|
# @note Generated method behavior:
|
382
|
-
# - Without args: Retrieves current value from Redis
|
383
|
-
# - With value: Sets and immediately persists to Redis
|
447
|
+
# - Without args: Retrieves current value from Valkey/Redis
|
448
|
+
# - With value: Sets and immediately persists to Valkey/Redis
|
384
449
|
# - Returns boolean indicating success for writes
|
385
450
|
# - Bypasses object-level caching and expiration updates
|
386
451
|
#
|
@@ -394,20 +459,20 @@ end
|
|
394
459
|
handle_method_conflict(fast_method_name, on_conflict) do
|
395
460
|
# Fast attribute accessor method for the '#{field_name}' attribute.
|
396
461
|
# This method provides immediate read and write access to the attribute
|
397
|
-
# in
|
462
|
+
# in the database.
|
398
463
|
#
|
399
464
|
# When called without arguments, it retrieves the current value of the
|
400
|
-
# attribute from
|
465
|
+
# attribute from the database.
|
401
466
|
# When called with an argument, it immediately persists the new value to
|
402
|
-
#
|
467
|
+
# the database.
|
403
468
|
#
|
404
469
|
# @overload #{method_name}
|
405
|
-
# Retrieves the current value of the attribute from
|
470
|
+
# Retrieves the current value of the attribute from the database.
|
406
471
|
# @return [Object] the current value of the attribute.
|
407
472
|
#
|
408
473
|
# @overload #{method_name}(value)
|
409
474
|
# Sets and immediately persists the new value of the attribute to
|
410
|
-
#
|
475
|
+
# the database.
|
411
476
|
# @param value [Object] the new value to set for the attribute.
|
412
477
|
# @return [Object] the newly set value.
|
413
478
|
#
|
@@ -416,7 +481,7 @@ end
|
|
416
481
|
# the method.
|
417
482
|
#
|
418
483
|
# @note This method bypasses any object-level caching and interacts
|
419
|
-
# directly with
|
484
|
+
# directly with the database. It does not trigger updates to other attributes
|
420
485
|
# or the object's expiration time.
|
421
486
|
#
|
422
487
|
# @example
|
@@ -437,7 +502,7 @@ end
|
|
437
502
|
|
438
503
|
begin
|
439
504
|
# Trace the operation if debugging is enabled.
|
440
|
-
Familia.trace :FAST_WRITER,
|
505
|
+
Familia.trace :FAST_WRITER, nil, "#{field_name}: #{val.inspect}" if Familia.debug?
|
441
506
|
|
442
507
|
# Convert the provided value to a format suitable for Database storage.
|
443
508
|
prepared = serialize_value(val)
|
@@ -1,14 +1,12 @@
|
|
1
|
-
# lib/familia/horreum/
|
2
|
-
|
3
|
-
require_relative 'related_fields_management'
|
1
|
+
# lib/familia/horreum/management.rb
|
4
2
|
|
5
3
|
module Familia
|
6
4
|
class Horreum
|
7
|
-
# ManagementMethods
|
8
|
-
# records.
|
5
|
+
# ManagementMethods - Class-level methods for Horreum model management
|
9
6
|
#
|
10
7
|
# This module is extended into classes that include Familia::Horreum,
|
11
|
-
# providing methods for
|
8
|
+
# providing class methods for database operations and object management
|
9
|
+
# (e.g., Customer.create, Customer.find_by_id)
|
12
10
|
#
|
13
11
|
# # Key features:
|
14
12
|
# * Includes RelatedFieldsManagement for DataType field handling
|
@@ -17,11 +15,13 @@ module Familia
|
|
17
15
|
module ManagementMethods
|
18
16
|
include Familia::Horreum::RelatedFieldsManagement # Provides DataType query methods
|
19
17
|
|
18
|
+
using Familia::Refinements::StylizeWords
|
19
|
+
|
20
20
|
# Creates and persists a new instance of the class.
|
21
21
|
#
|
22
|
-
# @param
|
22
|
+
# @param args [Array] Variable number of positional arguments to be passed
|
23
23
|
# to the constructor.
|
24
|
-
# @param
|
24
|
+
# @param kwargs [Hash] Keyword arguments to be passed to the constructor.
|
25
25
|
# @return [Object] The newly created and persisted instance.
|
26
26
|
# @raise [Familia::Problem] If an instance with the same identifier already
|
27
27
|
# exists.
|
@@ -68,10 +68,35 @@ module Familia
|
|
68
68
|
ids.collect! { |objid| dbkey(objid) }
|
69
69
|
return [] if ids.compact.empty?
|
70
70
|
|
71
|
-
Familia.trace :MULTIGET,
|
71
|
+
Familia.trace :MULTIGET, nil, "#{ids.size}: #{ids}" if Familia.debug?
|
72
72
|
dbclient.mget(*ids)
|
73
73
|
end
|
74
74
|
|
75
|
+
# Converts the class name into a string that can be used to look up
|
76
|
+
# configuration values. This is particularly useful when mapping
|
77
|
+
# familia models with specific database numbers in the configuration.
|
78
|
+
#
|
79
|
+
# Familia::Horreum::DefinitionMethods#config_name
|
80
|
+
#
|
81
|
+
# @example V2::Session.config_name => 'session'
|
82
|
+
#
|
83
|
+
# @return [String] The underscored class name as a string
|
84
|
+
def config_name
|
85
|
+
return nil if name.nil?
|
86
|
+
|
87
|
+
name.demodularize.snake_case
|
88
|
+
end
|
89
|
+
|
90
|
+
# Familia::Horreum::DefinitionMethods#familia_name
|
91
|
+
#
|
92
|
+
# @example V2::Session.config_name => 'Session'
|
93
|
+
#
|
94
|
+
def familia_name
|
95
|
+
return nil if name.nil?
|
96
|
+
|
97
|
+
name.demodularize
|
98
|
+
end
|
99
|
+
|
75
100
|
# Retrieves and instantiates an object from Database using the full object
|
76
101
|
# key.
|
77
102
|
#
|
@@ -83,7 +108,7 @@ module Familia
|
|
83
108
|
# This method performs a two-step process to safely retrieve and
|
84
109
|
# instantiate objects:
|
85
110
|
#
|
86
|
-
# 1. It first checks if the key exists in
|
111
|
+
# 1. It first checks if the key exists in the database. This is crucial because:
|
87
112
|
# - It provides a definitive answer about the object's existence.
|
88
113
|
# - It prevents ambiguity that could arise from `hgetall` returning an
|
89
114
|
# empty hash for non-existent keys, which could lead to the creation
|
@@ -93,7 +118,7 @@ module Familia
|
|
93
118
|
# it.
|
94
119
|
#
|
95
120
|
# This approach ensures that we only attempt to instantiate objects that
|
96
|
-
# actually exist in Redis, improving reliability and simplifying
|
121
|
+
# actually exist in Valkey/Redis, improving reliability and simplifying
|
97
122
|
# debugging.
|
98
123
|
#
|
99
124
|
# @example
|
@@ -108,17 +133,17 @@ module Familia
|
|
108
133
|
does_exist = dbclient.exists(objkey).positive?
|
109
134
|
|
110
135
|
Familia.ld "[.find_by_key] #{self} from key #{objkey} (exists: #{does_exist})"
|
111
|
-
Familia.trace :FROM_KEY,
|
136
|
+
Familia.trace :FROM_KEY, nil, objkey if Familia.debug?
|
112
137
|
|
113
138
|
# This is the reason for calling exists first. We want to definitively
|
114
|
-
# and without any ambiguity know if the object exists in
|
139
|
+
# and without any ambiguity know if the object exists in the database. If it
|
115
140
|
# doesn't, we return nil. If it does, we proceed to load the object.
|
116
141
|
# Otherwise, hgetall will return an empty hash, which will be passed to
|
117
142
|
# the constructor, which will then be annoying to debug.
|
118
143
|
return unless does_exist
|
119
144
|
|
120
145
|
obj = dbclient.hgetall(objkey) # horreum objects are persisted as database hashes
|
121
|
-
Familia.trace :FROM_KEY2,
|
146
|
+
Familia.trace :FROM_KEY2, nil, "#{objkey}: #{obj.inspect}" if Familia.debug?
|
122
147
|
|
123
148
|
new(**obj)
|
124
149
|
end
|
@@ -150,33 +175,34 @@ module Familia
|
|
150
175
|
objkey = dbkey(identifier, suffix)
|
151
176
|
|
152
177
|
Familia.ld "[.find_by_id] #{self} from key #{objkey})"
|
153
|
-
Familia.trace :FIND_BY_ID,
|
178
|
+
Familia.trace :FIND_BY_ID, nil, objkey if Familia.debug?
|
154
179
|
find_by_key objkey
|
155
180
|
end
|
156
181
|
alias find find_by_id
|
157
182
|
alias load find_by_id # deprecated
|
158
183
|
alias from_identifier find_by_id # deprecated
|
159
184
|
|
160
|
-
# Checks if an object with the given identifier exists in
|
185
|
+
# Checks if an object with the given identifier exists in the database.
|
161
186
|
#
|
162
187
|
# @param identifier [String, Integer] The unique identifier for the object.
|
163
188
|
# @param suffix [Symbol, nil] The suffix to use in the dbkey (default: class suffix).
|
164
189
|
# @return [Boolean] true if the object exists, false otherwise.
|
165
190
|
#
|
166
191
|
# This method constructs the full dbkey using the provided identifier and suffix,
|
167
|
-
# then checks if the key exists in
|
192
|
+
# then checks if the key exists in the database.
|
168
193
|
#
|
169
194
|
# @example
|
170
|
-
# User.exists?(123) # Returns true if user:123:object exists in Redis
|
195
|
+
# User.exists?(123) # Returns true if user:123:object exists in Valkey/Redis
|
171
196
|
#
|
172
197
|
def exists?(identifier, suffix = nil)
|
173
|
-
raise NoIdentifier,
|
198
|
+
raise NoIdentifier, 'Empty identifier' if identifier.to_s.empty?
|
199
|
+
|
174
200
|
suffix ||= self.suffix
|
175
201
|
|
176
202
|
objkey = dbkey identifier, suffix
|
177
203
|
|
178
204
|
ret = dbclient.exists objkey
|
179
|
-
Familia.trace :EXISTS,
|
205
|
+
Familia.trace :EXISTS, nil, "#{objkey} #{ret.inspect}" if Familia.debug?
|
180
206
|
|
181
207
|
ret.positive? # differs from Valkey API but I think it's okay bc `exists?` is a predicate method.
|
182
208
|
end
|
@@ -193,17 +219,36 @@ module Familia
|
|
193
219
|
# `delete!` when working directly with dbkeys.
|
194
220
|
#
|
195
221
|
# @example
|
196
|
-
# User.destroy!(123) # Removes user:123:object from Redis
|
222
|
+
# User.destroy!(123) # Removes user:123:object from Valkey/Redis
|
197
223
|
#
|
198
224
|
def destroy!(identifier, suffix = nil)
|
199
225
|
suffix ||= self.suffix
|
200
|
-
return false if identifier.to_s.empty?
|
226
|
+
return MultiResult.new(false, []) if identifier.to_s.empty?
|
201
227
|
|
202
228
|
objkey = dbkey identifier, suffix
|
203
229
|
|
204
|
-
|
205
|
-
|
206
|
-
|
230
|
+
# Execute all deletion operations within a transaction
|
231
|
+
transaction do |conn|
|
232
|
+
# Clean up related fields first to avoid orphaned keys
|
233
|
+
if relations?
|
234
|
+
Familia.trace :DESTROY_RELATIONS!, nil, "#{self} has relations: #{related_fields.keys}" if Familia.debug?
|
235
|
+
|
236
|
+
# Create a temporary instance to access related fields.
|
237
|
+
# Pass identifier in constructor so init() sees it and can set dependent fields.
|
238
|
+
identifier_field_name = self.identifier_field
|
239
|
+
temp_instance = identifier_field_name ? new(identifier_field_name => identifier.to_s) : new
|
240
|
+
|
241
|
+
related_fields.each do |name, _definition|
|
242
|
+
obj = temp_instance.send(name)
|
243
|
+
Familia.trace :DESTROY_RELATION!, name, "Deleting related field #{name} (#{obj.dbkey})" if Familia.debug?
|
244
|
+
conn.del(obj.dbkey)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Delete the main object key
|
249
|
+
ret = conn.del(objkey)
|
250
|
+
Familia.trace :DESTROY!, nil, "#{objkey} #{ret.inspect}" if Familia.debug?
|
251
|
+
end
|
207
252
|
end
|
208
253
|
|
209
254
|
# Finds all keys in Database matching the given suffix pattern.
|
@@ -249,7 +294,7 @@ module Familia
|
|
249
294
|
end
|
250
295
|
|
251
296
|
def any?(filter = '*')
|
252
|
-
matching_keys_count(filter)
|
297
|
+
matching_keys_count(filter).positive?
|
253
298
|
end
|
254
299
|
|
255
300
|
# Returns the number of dbkeys matching the given filter pattern
|
@@ -259,7 +304,8 @@ module Familia
|
|
259
304
|
def matching_keys_count(filter = '*')
|
260
305
|
dbclient.keys(dbkey(filter)).compact.size
|
261
306
|
end
|
262
|
-
alias size matching_keys_count
|
307
|
+
alias size matching_keys_count
|
308
|
+
alias length matching_keys_count
|
263
309
|
end
|
264
310
|
end
|
265
311
|
end
|