familia 2.0.0.pre16 → 2.0.0.pre18
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-smellage.yml → code-smells.yml} +3 -63
- data/.gitignore +2 -0
- data/.rubocop.yml +6 -0
- data/CHANGELOG.rst +82 -0
- data/CLAUDE.md +47 -2
- data/Gemfile.lock +1 -1
- data/README.md +13 -0
- data/bin/irb +1 -1
- data/docs/archive/FAMILIA_TECHNICAL.md +1 -1
- data/docs/guides/core-field-system.md +48 -26
- data/docs/migrating/v2.0.0-pre18.md +58 -0
- data/docs/overview.md +2 -2
- data/docs/qodo-merge-compliance.md +96 -0
- data/docs/reference/api-technical.md +1 -1
- data/examples/encrypted_fields.rb +1 -1
- data/examples/safe_dump.rb +1 -1
- data/lib/familia/base.rb +6 -6
- data/lib/familia/connection/middleware.rb +58 -4
- data/lib/familia/connection.rb +1 -1
- data/lib/familia/data_type/class_methods.rb +63 -0
- data/lib/familia/data_type/connection.rb +83 -0
- data/lib/familia/data_type/{commands.rb → database_commands.rb} +2 -2
- data/lib/familia/data_type/serialization.rb +5 -5
- data/lib/familia/data_type/settings.rb +96 -0
- data/lib/familia/data_type/types/hashkey.rb +2 -1
- data/lib/familia/data_type/types/sorted_set.rb +113 -10
- data/lib/familia/data_type/types/stringkey.rb +0 -4
- data/lib/familia/data_type.rb +8 -195
- data/lib/familia/encryption/encrypted_data.rb +12 -2
- data/lib/familia/encryption/manager.rb +11 -4
- data/lib/familia/features/autoloader.rb +3 -1
- data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
- data/lib/familia/features/encrypted_fields.rb +5 -2
- data/lib/familia/features/external_identifier.rb +49 -8
- data/lib/familia/features/object_identifier.rb +84 -12
- data/lib/familia/features/relationships/indexing/multi_index_generators.rb +9 -9
- data/lib/familia/features/relationships/indexing/unique_index_generators.rb +45 -26
- data/lib/familia/features/relationships/indexing.rb +7 -1
- data/lib/familia/features/relationships/participation/participant_methods.rb +6 -2
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/features/transient_fields.rb +7 -2
- data/lib/familia/features.rb +6 -1
- data/lib/familia/field_type.rb +0 -18
- data/lib/familia/horreum/{core/connection.rb → connection.rb} +21 -0
- data/lib/familia/horreum/{core/database_commands.rb → database_commands.rb} +1 -1
- data/lib/familia/horreum/{subclass/definition.rb → definition.rb} +102 -56
- data/lib/familia/horreum/{subclass/management.rb → management.rb} +18 -15
- data/lib/familia/horreum/{core/serialization.rb → persistence.rb} +73 -170
- data/lib/familia/horreum/{subclass/related_fields_management.rb → related_fields.rb} +22 -2
- data/lib/familia/horreum/serialization.rb +190 -0
- data/lib/familia/horreum.rb +39 -14
- data/lib/familia/identifier_extractor.rb +60 -0
- data/lib/familia/logging.rb +271 -112
- data/lib/familia/refinements.rb +0 -1
- data/lib/familia/version.rb +1 -1
- data/lib/familia.rb +2 -2
- data/lib/middleware/{database_middleware.rb → database_logger.rb} +47 -14
- data/pr_agent.toml +31 -0
- data/pr_compliance_checklist.yaml +45 -0
- data/try/edge_cases/empty_identifiers_try.rb +1 -1
- data/try/edge_cases/hash_symbolization_try.rb +31 -31
- data/try/edge_cases/json_serialization_try.rb +2 -2
- data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
- data/try/edge_cases/race_conditions_try.rb +1 -1
- data/try/edge_cases/reserved_keywords_try.rb +1 -1
- data/try/edge_cases/string_coercion_try.rb +1 -1
- data/try/edge_cases/ttl_side_effects_try.rb +1 -1
- data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
- data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
- data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
- data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
- data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
- data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
- data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
- data/try/features/encrypted_fields/memory_security_try.rb +1 -1
- data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
- data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
- data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
- data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
- data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
- data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
- data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
- data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
- data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
- data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
- data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
- data/try/features/expiration/expiration_try.rb +1 -1
- data/try/features/external_identifier/external_identifier_try.rb +1 -1
- data/try/features/feature_dependencies_try.rb +1 -1
- data/try/features/feature_improvements_try.rb +1 -1
- data/try/features/field_groups_try.rb +244 -0
- data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
- data/try/features/object_identifier/object_identifier_try.rb +1 -1
- data/try/features/quantization/quantization_try.rb +1 -1
- data/try/features/real_feature_integration_try.rb +17 -14
- data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
- data/try/features/relationships/indexing_try.rb +16 -1
- data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
- data/try/features/relationships/participation_commands_verification_try.rb +4 -4
- data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
- data/try/features/relationships/participation_reverse_index_try.rb +1 -1
- data/try/features/relationships/relationships_api_changes_try.rb +1 -1
- data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
- data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
- data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
- data/try/features/relationships/relationships_performance_try.rb +1 -1
- data/try/features/relationships/relationships_performance_working_try.rb +1 -1
- data/try/features/relationships/relationships_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
- data/try/features/safe_dump/safe_dump_try.rb +1 -1
- data/try/features/transient_fields/redacted_string_try.rb +1 -1
- data/try/features/transient_fields/refresh_reset_try.rb +3 -1
- data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
- data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +1 -1
- data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
- data/try/{core → integration/connection}/isolated_dbclient_try.rb +3 -3
- data/try/integration/connection/middleware_reconnect_try.rb +87 -0
- data/try/{connection → integration/connection}/operation_mode_guards_try.rb +1 -1
- data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +1 -1
- data/try/{core → integration/connection}/pools_try.rb +1 -1
- data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
- data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
- data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
- data/try/{core → integration}/create_method_try.rb +1 -1
- data/try/integration/cross_component_try.rb +1 -1
- data/try/{core → integration}/database_consistency_try.rb +12 -8
- data/try/{core → integration}/familia_extended_try.rb +1 -1
- data/try/{core → integration}/familia_members_methods_try.rb +1 -1
- data/try/{models → integration/models}/customer_safe_dump_try.rb +1 -1
- data/try/{models → integration/models}/customer_try.rb +6 -6
- data/try/{models → integration/models}/datatype_base_try.rb +1 -1
- data/try/{models → integration/models}/familia_object_try.rb +1 -1
- data/try/{core → integration}/persistence_operations_try.rb +1 -1
- data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
- data/try/{configuration → integration}/scenarios_try.rb +2 -2
- data/try/{core → integration}/secure_identifier_try.rb +1 -1
- data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
- data/try/performance/benchmarks_try.rb +2 -2
- data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
- data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
- data/try/{helpers → support/helpers}/test_helpers.rb +15 -7
- data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +1 -1
- data/try/{core → unit/core}/autoloader_try.rb +1 -1
- data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
- data/try/{core → unit/core}/connection_try.rb +5 -5
- data/try/{core → unit/core}/errors_try.rb +4 -4
- data/try/{core → unit/core}/extensions_try.rb +1 -1
- data/try/unit/core/familia_logger_try.rb +110 -0
- data/try/{core → unit/core}/familia_try.rb +2 -2
- data/try/{core → unit/core}/middleware_try.rb +41 -1
- data/try/{core → unit/core}/settings_try.rb +1 -1
- data/try/{core → unit/core}/time_utils_try.rb +1 -1
- data/try/{core → unit/core}/tools_try.rb +3 -3
- data/try/{core → unit/core}/utils_try.rb +17 -14
- data/try/{data_types → unit/data_types}/boolean_try.rb +1 -1
- data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
- data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
- data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
- data/try/{data_types → unit/data_types}/list_try.rb +1 -1
- data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
- data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
- data/try/unit/data_types/sorted_set_zadd_options_try.rb +625 -0
- data/try/{data_types → unit/data_types}/string_try.rb +1 -1
- data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
- data/try/unit/horreum/auto_indexing_on_save_try.rb +212 -0
- data/try/{horreum → unit/horreum}/base_try.rb +3 -3
- data/try/{horreum → unit/horreum}/class_methods_try.rb +1 -1
- data/try/{horreum → unit/horreum}/commands_try.rb +3 -1
- data/try/unit/horreum/defensive_initialization_try.rb +86 -0
- data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +3 -1
- data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
- data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
- data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
- data/try/{horreum → unit/horreum}/initialization_try.rb +2 -2
- data/try/unit/horreum/json_type_preservation_try.rb +248 -0
- data/try/{horreum → unit/horreum}/relations_try.rb +1 -1
- data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
- data/try/{horreum → unit/horreum}/serialization_try.rb +4 -4
- data/try/{horreum → unit/horreum}/settings_try.rb +3 -1
- data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
- data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
- data/try/valkey.conf +26 -0
- metadata +149 -132
- data/lib/familia/distinguisher.rb +0 -85
- data/lib/familia/horreum/core.rb +0 -21
- data/lib/familia/refinements/logger_trace.rb +0 -60
- data/try/refinements/logger_trace_methods_try.rb +0 -44
- /data/lib/familia/horreum/{shared/settings.rb → settings.rb} +0 -0
- /data/lib/familia/horreum/{core/utils.rb → utils.rb} +0 -0
- /data/try/{debugging → support/debugging}/README.md +0 -0
- /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
- /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
- /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
- /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
- /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
- /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
- /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
- /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
- /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
- /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
@@ -33,9 +33,12 @@ module Familia
|
|
33
33
|
# - Employee.rebuild_email_index
|
34
34
|
#
|
35
35
|
# Generates on Employee (self):
|
36
|
-
# - employee.add_to_class_email_index
|
36
|
+
# - employee.add_to_class_email_index (called automatically on save)
|
37
37
|
# - employee.remove_from_class_email_index
|
38
38
|
# - employee.update_in_class_email_index(old_email)
|
39
|
+
#
|
40
|
+
# Note: Class-level indexes auto-populate on save(). Instance-scoped indexes
|
41
|
+
# (with within:) remain manual as they require parent context.
|
39
42
|
module UniqueIndexGenerators
|
40
43
|
module_function
|
41
44
|
|
@@ -101,35 +104,42 @@ module Familia
|
|
101
104
|
|
102
105
|
# Generate instance query method (e.g., company.find_by_badge_number)
|
103
106
|
actual_target_class.class_eval do
|
104
|
-
define_method("find_by_#{field}") do |
|
107
|
+
define_method(:"find_by_#{field}") do |provided_value|
|
105
108
|
# Use declared field accessor instead of manual instantiation
|
106
109
|
index_hash = send(index_name)
|
107
110
|
|
108
|
-
# Get the identifier from the hash
|
109
|
-
|
110
|
-
|
111
|
+
# Get the identifier from the hash using .get method.
|
112
|
+
# We use .get instead of [] because it's part of the standard interface
|
113
|
+
# common across all DataType classes (List, UnsortedSet, SortedSet, HashKey).
|
114
|
+
# While unique indexes always use HashKey, using .get maintains consistency
|
115
|
+
# with the broader DataType API patterns used throughout Familia.
|
116
|
+
record_id = index_hash.get(provided_value)
|
117
|
+
return nil unless record_id
|
111
118
|
|
112
|
-
indexed_class.
|
119
|
+
indexed_class.find_by_identifier(record_id)
|
113
120
|
end
|
114
121
|
|
115
122
|
# Generate bulk query method (e.g., company.find_all_by_badge_number)
|
116
|
-
define_method("find_all_by_#{field}") do |
|
117
|
-
|
123
|
+
define_method(:"find_all_by_#{field}") do |provided_ids|
|
124
|
+
provided_ids = Array(provided_ids)
|
125
|
+
return [] if provided_ids.empty?
|
118
126
|
|
119
127
|
# Use declared field accessor instead of manual instantiation
|
120
128
|
index_hash = send(index_name)
|
121
129
|
|
122
130
|
# Get all identifiers from the hash
|
123
|
-
|
131
|
+
record_ids = index_hash.values_at(*provided_ids.map(&:to_s))
|
124
132
|
# Filter out nil values and instantiate objects
|
125
|
-
|
133
|
+
record_ids.compact.map { |record_id|
|
134
|
+
indexed_class.find_by_identifier(record_id)
|
135
|
+
}
|
126
136
|
end
|
127
137
|
|
128
138
|
# Accessor method already created by ensure_index_field above
|
129
139
|
# No need to manually define it here
|
130
140
|
|
131
141
|
# Generate method to rebuild the unique index for this parent instance
|
132
|
-
define_method("rebuild_#{index_name}") do
|
142
|
+
define_method(:"rebuild_#{index_name}") do
|
133
143
|
# Use declared field accessor instead of manual instantiation
|
134
144
|
index_hash = send(index_name)
|
135
145
|
|
@@ -156,7 +166,7 @@ module Familia
|
|
156
166
|
def generate_mutation_methods_self(indexed_class, field, target_class, index_name)
|
157
167
|
target_class_config = target_class.config_name
|
158
168
|
indexed_class.class_eval do
|
159
|
-
method_name = "add_to_#{target_class_config}_#{index_name}"
|
169
|
+
method_name = :"add_to_#{target_class_config}_#{index_name}"
|
160
170
|
Familia.ld("[UniqueIndexGenerators] #{name} method #{method_name}")
|
161
171
|
|
162
172
|
define_method(method_name) do |target_instance|
|
@@ -172,7 +182,7 @@ module Familia
|
|
172
182
|
index_hash[field_value.to_s] = identifier
|
173
183
|
end
|
174
184
|
|
175
|
-
method_name = "remove_from_#{target_class_config}_#{index_name}"
|
185
|
+
method_name = :"remove_from_#{target_class_config}_#{index_name}"
|
176
186
|
Familia.ld("[UniqueIndexGenerators] #{name} method #{method_name}")
|
177
187
|
|
178
188
|
define_method(method_name) do |target_instance|
|
@@ -188,7 +198,7 @@ module Familia
|
|
188
198
|
index_hash.remove(field_value.to_s)
|
189
199
|
end
|
190
200
|
|
191
|
-
method_name = "update_in_#{target_class_config}_#{index_name}"
|
201
|
+
method_name = :"update_in_#{target_class_config}_#{index_name}"
|
192
202
|
Familia.ld("[UniqueIndexGenerators] #{name} method #{method_name}")
|
193
203
|
|
194
204
|
define_method(method_name) do |target_instance, old_field_value = nil|
|
@@ -218,30 +228,39 @@ module Familia
|
|
218
228
|
# - Employee.email_index
|
219
229
|
# - Employee.rebuild_email_index
|
220
230
|
def generate_query_methods_class(field, index_name, indexed_class)
|
221
|
-
indexed_class.define_singleton_method("find_by_#{field}") do |
|
231
|
+
indexed_class.define_singleton_method(:"find_by_#{field}") do |provided_id|
|
222
232
|
index_hash = send(index_name) # Access the class-level hashkey DataType
|
223
|
-
object_id = index_hash[field_value.to_s]
|
224
233
|
|
225
|
-
|
234
|
+
# Get the identifier from the hash using .get method.
|
235
|
+
# We use .get instead of [] because it's part of the standard interface
|
236
|
+
# common across all DataType classes (List, UnsortedSet, SortedSet, HashKey).
|
237
|
+
# While unique indexes always use HashKey, using .get maintains consistency
|
238
|
+
# with the broader DataType API patterns used throughout Familia.
|
239
|
+
record_id = index_hash.get(provided_id)
|
240
|
+
|
241
|
+
return nil unless record_id
|
226
242
|
|
227
|
-
|
243
|
+
indexed_class.find_by_identifier(record_id)
|
228
244
|
end
|
229
245
|
|
230
246
|
# Generate class-level bulk query method
|
231
|
-
indexed_class.define_singleton_method("find_all_by_#{field}") do |
|
232
|
-
|
247
|
+
indexed_class.define_singleton_method(:"find_all_by_#{field}") do |provided_ids|
|
248
|
+
provided_ids = Array(provided_ids)
|
249
|
+
return [] if provided_ids.empty?
|
233
250
|
|
234
251
|
index_hash = send(index_name) # Access the class-level hashkey DataType
|
235
|
-
|
252
|
+
record_ids = index_hash.values_at(*provided_ids.map(&:to_s))
|
236
253
|
# Filter out nil values and instantiate objects
|
237
|
-
|
254
|
+
record_ids.compact.map { |record_id|
|
255
|
+
indexed_class.find_by_identifier(record_id)
|
256
|
+
}
|
238
257
|
end
|
239
258
|
|
240
259
|
# The index accessor method is already created by the class_hashkey declaration
|
241
260
|
# No need to manually create it - Horreum handles this automatically
|
242
261
|
|
243
262
|
# Generate method to rebuild the class-level index
|
244
|
-
indexed_class.define_singleton_method("rebuild_#{index_name}") do
|
263
|
+
indexed_class.define_singleton_method(:"rebuild_#{index_name}") do
|
245
264
|
index_hash = send(index_name) # Access the class-level hashkey DataType
|
246
265
|
|
247
266
|
# Clear existing index using DataType method
|
@@ -260,7 +279,7 @@ module Familia
|
|
260
279
|
# - employee.update_in_class_email_index(old_email)
|
261
280
|
def generate_mutation_methods_class(field, index_name, indexed_class)
|
262
281
|
indexed_class.class_eval do
|
263
|
-
define_method("add_to_class_#{index_name}") do
|
282
|
+
define_method(:"add_to_class_#{index_name}") do
|
264
283
|
index_hash = self.class.send(index_name) # Access the class-level hashkey DataType
|
265
284
|
field_value = send(field)
|
266
285
|
|
@@ -269,7 +288,7 @@ module Familia
|
|
269
288
|
index_hash[field_value.to_s] = identifier
|
270
289
|
end
|
271
290
|
|
272
|
-
define_method("remove_from_class_#{index_name}") do
|
291
|
+
define_method(:"remove_from_class_#{index_name}") do
|
273
292
|
index_hash = self.class.send(index_name) # Access the class-level hashkey DataType
|
274
293
|
field_value = send(field)
|
275
294
|
|
@@ -278,7 +297,7 @@ module Familia
|
|
278
297
|
index_hash.remove(field_value.to_s)
|
279
298
|
end
|
280
299
|
|
281
|
-
define_method("update_in_class_#{index_name}") do |old_field_value = nil|
|
300
|
+
define_method(:"update_in_class_#{index_name}") do |old_field_value = nil|
|
282
301
|
new_field_value = send(field)
|
283
302
|
|
284
303
|
# Use class-level transaction for atomicity with DataType abstraction
|
@@ -18,7 +18,7 @@ module Familia
|
|
18
18
|
# end
|
19
19
|
#
|
20
20
|
# user = User.new(user_id: 'u1', email: 'alice@example.com')
|
21
|
-
# user.
|
21
|
+
# user.save # Automatically populates email_lookup index
|
22
22
|
# User.find_by_email('alice@example.com') # → user
|
23
23
|
#
|
24
24
|
# @example Instance-scoped unique index (within parent, 1:1 via HashKey)
|
@@ -58,6 +58,12 @@ module Familia
|
|
58
58
|
# - Instance unique: "company:c1:badge_index" → HashKey
|
59
59
|
# - Instance multi: "company:c1:dept_index:engineering" → UnsortedSet
|
60
60
|
#
|
61
|
+
# Auto-Indexing:
|
62
|
+
# Class-level unique_index declarations automatically populate on save():
|
63
|
+
# user = User.new(email: 'test@example.com')
|
64
|
+
# user.save # Auto-indexes email → user_id
|
65
|
+
# Instance-scoped indexes (with within:) remain manual (require parent context).
|
66
|
+
#
|
61
67
|
# Design Philosophy:
|
62
68
|
# Indexing is for finding objects by attribute, not ordering them.
|
63
69
|
# Use multi_index with UnsortedSet (no temporal scores), then sort in Ruby:
|
@@ -23,8 +23,10 @@ module Familia
|
|
23
23
|
# ├── add_to_customer_domains(customer, score) # Add myself to customer's domains
|
24
24
|
# ├── remove_from_customer_domains(customer) # Remove myself from customer's domains
|
25
25
|
# ├── score_in_customer_domains(customer) # Get my score (sorted_set only)
|
26
|
-
# ├── update_score_in_customer_domains(customer) # Update my score (sorted_set only)
|
27
26
|
# └── position_in_customer_domains(customer) # Get my position (list only)
|
27
|
+
#
|
28
|
+
# Note: To update scores, use the DataType API directly:
|
29
|
+
# customer.domains.add(domain.identifier, new_score, xx: true)
|
28
30
|
|
29
31
|
module Builder
|
30
32
|
extend CollectionOperations
|
@@ -126,7 +128,9 @@ module Familia
|
|
126
128
|
|
127
129
|
# Build score-related methods for sorted sets
|
128
130
|
# Creates: domain.score_in_customer_domains(customer)
|
129
|
-
#
|
131
|
+
#
|
132
|
+
# Note: Score updates use DataType API directly:
|
133
|
+
# customer.domains.add(domain.identifier, new_score, xx: true)
|
130
134
|
def self.build_score_methods(participant_class, target_name, collection_name)
|
131
135
|
# Get score method
|
132
136
|
score_method = "score_in_#{target_name}_#{collection_name}"
|
@@ -1,9 +1,8 @@
|
|
1
1
|
# lib/familia/features/safe_dump.rb
|
2
2
|
|
3
3
|
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# at runtime, so thread safety is not a concern for this feature.
|
4
|
+
# Class instance variables are used here for configuration. These are set
|
5
|
+
# once at loadtime and not mutated, so thread safety is not an issue here.
|
7
6
|
#
|
8
7
|
module Familia
|
9
8
|
module Features
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# lib/familia/features/transient_fields.rb
|
2
2
|
|
3
3
|
require_relative 'transient_fields/redacted_string'
|
4
|
+
require_relative 'transient_fields/transient_field_type'
|
4
5
|
|
5
6
|
module Familia
|
6
7
|
module Features
|
@@ -104,7 +105,7 @@ module Familia
|
|
104
105
|
# (HashiCorp Vault, AWS Secrets Manager) or languages with secure memory handling.
|
105
106
|
#
|
106
107
|
module TransientFields
|
107
|
-
Familia::Base.add_feature self, :transient_fields, depends_on: nil
|
108
|
+
Familia::Base.add_feature self, :transient_fields, depends_on: nil, field_group: :transient_fields
|
108
109
|
|
109
110
|
def self.included(base)
|
110
111
|
Familia.trace :LOADED, self, base if Familia.debug?
|
@@ -143,8 +144,12 @@ module Familia
|
|
143
144
|
@transient_fields ||= []
|
144
145
|
@transient_fields << name unless @transient_fields.include?(name)
|
145
146
|
|
147
|
+
# Add to field_groups if the group exists
|
148
|
+
if field_groups&.key?(:transient_fields)
|
149
|
+
field_groups[:transient_fields] << name
|
150
|
+
end
|
151
|
+
|
146
152
|
# Use the field type system for proper integration
|
147
|
-
require_relative 'transient_fields/transient_field_type'
|
148
153
|
field_type = TransientFieldType.new(name, as: as, **kwargs.merge(fast_method: false))
|
149
154
|
register_field_type(field_type)
|
150
155
|
end
|
data/lib/familia/features.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
require_relative 'features/autoloader'
|
5
5
|
|
6
6
|
module Familia
|
7
|
-
FeatureDefinition = Data.define(:name, :depends_on)
|
7
|
+
FeatureDefinition = Data.define(:name, :depends_on, :field_group)
|
8
8
|
|
9
9
|
# Familia::Features
|
10
10
|
#
|
@@ -147,6 +147,11 @@ module Familia
|
|
147
147
|
calling_location = caller_locations(1, 1)&.first
|
148
148
|
options[:calling_location] = calling_location&.path
|
149
149
|
|
150
|
+
# Initialize field group if feature declares one
|
151
|
+
if feature_def&.field_group && respond_to?(:field_group)
|
152
|
+
field_group(feature_def.field_group)
|
153
|
+
end
|
154
|
+
|
150
155
|
# Add feature options if the class supports them (Horreum classes)
|
151
156
|
add_feature_options(feature_name, **options) if respond_to?(:add_feature_options)
|
152
157
|
|
data/lib/familia/field_type.rb
CHANGED
@@ -117,25 +117,7 @@ module Familia
|
|
117
117
|
klass.define_method :"#{method_name}=" do |value|
|
118
118
|
instance_variable_set(:"@#{field_name}", value)
|
119
119
|
|
120
|
-
# If this field is the identifier and object_identifier feature is loaded,
|
121
|
-
# update objid_lookup mapping when identifier is set after objid generation
|
122
|
-
if respond_to?(:objid) &&
|
123
|
-
self.class.respond_to?(:identifier_field) &&
|
124
|
-
self.class.identifier_field == field_name &&
|
125
|
-
self.class.respond_to?(:objid_lookup)
|
126
|
-
current_objid = instance_variable_get(:@objid)
|
127
|
-
self.class.objid_lookup[current_objid] = value if current_objid && value
|
128
|
-
end
|
129
120
|
|
130
|
-
# If this field is the identifier and external_identifier feature is loaded,
|
131
|
-
# update extid_lookup mapping when identifier is set after extid generation
|
132
|
-
if respond_to?(:extid) &&
|
133
|
-
self.class.respond_to?(:identifier_field) &&
|
134
|
-
self.class.identifier_field == field_name &&
|
135
|
-
self.class.respond_to?(:extid_lookup)
|
136
|
-
current_extid = instance_variable_get(:@extid)
|
137
|
-
self.class.extid_lookup[current_extid] = value if current_extid && value
|
138
|
-
end
|
139
121
|
end
|
140
122
|
end
|
141
123
|
end
|
@@ -149,6 +149,7 @@ module Familia
|
|
149
149
|
# @see MultiResult For details on the return value structure
|
150
150
|
# @see #batch_update For similar atomic field updates with MultiResult
|
151
151
|
def transaction(&)
|
152
|
+
ensure_relatives_initialized!
|
152
153
|
Familia::Connection::TransactionCore.execute_transaction(-> { dbclient }, &)
|
153
154
|
end
|
154
155
|
alias multi transaction
|
@@ -243,12 +244,32 @@ module Familia
|
|
243
244
|
# @see MultiResult For details on the return value structure
|
244
245
|
# @see Familia.transaction For atomic command execution
|
245
246
|
def pipelined(&block)
|
247
|
+
ensure_relatives_initialized!
|
246
248
|
Familia::Connection::PipelineCore.execute_pipeline(-> { dbclient }, &block)
|
247
249
|
end
|
248
250
|
alias pipeline pipelined
|
249
251
|
|
250
252
|
private
|
251
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
|
+
|
252
273
|
# Builds the class-level connection chain with handlers in priority order
|
253
274
|
def build_connection_chain
|
254
275
|
# Cache handlers at class level to avoid creating new instances per model instance
|
@@ -1,7 +1,8 @@
|
|
1
|
-
# lib/familia/horreum/
|
1
|
+
# lib/familia/horreum/definition.rb
|
2
2
|
|
3
|
-
require_relative '
|
4
|
-
|
3
|
+
require_relative 'settings'
|
4
|
+
|
5
|
+
require_relative '../field_type'
|
5
6
|
|
6
7
|
module Familia
|
7
8
|
VALID_STRATEGIES = %i[raise skip ignore warn overwrite].freeze
|
@@ -28,9 +29,9 @@ module Familia
|
|
28
29
|
@related_fields = nil
|
29
30
|
@default_expiration = nil
|
30
31
|
|
31
|
-
#
|
32
|
-
@
|
33
|
-
@
|
32
|
+
# Field groups
|
33
|
+
@field_groups = nil
|
34
|
+
@current_field_group = nil
|
34
35
|
|
35
36
|
# DefinitionMethods - Class-level DSL methods for defining Horreum model structure
|
36
37
|
#
|
@@ -48,6 +49,84 @@ module Familia
|
|
48
49
|
include Familia::Settings
|
49
50
|
include Familia::Horreum::RelatedFieldsManagement # Provides DataType field methods
|
50
51
|
|
52
|
+
# Defines a field group to organize related fields.
|
53
|
+
#
|
54
|
+
# Field groups provide a way to categorize and query fields by purpose or feature.
|
55
|
+
# When a block is provided, fields defined within the block are automatically
|
56
|
+
# added to the group. Without a block, an empty group is initialized.
|
57
|
+
#
|
58
|
+
# @param name [Symbol, String] the name of the field group
|
59
|
+
# @yield optional block for defining fields within the group
|
60
|
+
# @return [Array<Symbol>] the array of field names in the group
|
61
|
+
#
|
62
|
+
# @raise [Familia::Problem] if attempting to nest field groups
|
63
|
+
#
|
64
|
+
# @example Manual field grouping
|
65
|
+
# class User < Familia::Horreum
|
66
|
+
# field_group :personal_info do
|
67
|
+
# field :name
|
68
|
+
# field :email
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# User.personal_info # => [:name, :email]
|
73
|
+
#
|
74
|
+
# @example Initialize empty group
|
75
|
+
# class User < Familia::Horreum
|
76
|
+
# field_group :placeholder
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# User.placeholder # => []
|
80
|
+
#
|
81
|
+
def field_group(name, &block)
|
82
|
+
|
83
|
+
# Prevent nested field groups
|
84
|
+
if @current_field_group
|
85
|
+
raise Familia::Problem,
|
86
|
+
"Cannot define field group :#{name} while :#{@current_field_group} is being defined. " \
|
87
|
+
"Nested field groups are not supported."
|
88
|
+
end
|
89
|
+
|
90
|
+
# Initialize group
|
91
|
+
field_groups[name.to_sym] ||= []
|
92
|
+
|
93
|
+
if block_given?
|
94
|
+
@current_field_group = name.to_sym
|
95
|
+
begin
|
96
|
+
instance_eval(&block)
|
97
|
+
ensure
|
98
|
+
@current_field_group = nil
|
99
|
+
end
|
100
|
+
else
|
101
|
+
Familia.ld "[field_group] Created field group :#{name} but no block given" if Familia.debug?
|
102
|
+
end
|
103
|
+
|
104
|
+
field_groups[name.to_sym]
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns the list of all field group names defined for the class.
|
108
|
+
#
|
109
|
+
# @return [Array<Symbol>] array of field group names
|
110
|
+
#
|
111
|
+
# @example
|
112
|
+
# class User < Familia::Horreum
|
113
|
+
# field_group :personal_info do
|
114
|
+
# field :name
|
115
|
+
# end
|
116
|
+
# field_group :metadata do
|
117
|
+
# field :created_at
|
118
|
+
# end
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# User.field_groups # => [
|
122
|
+
# :personal_info => [...],
|
123
|
+
# :metadata => [..]
|
124
|
+
# ]
|
125
|
+
#
|
126
|
+
def field_groups
|
127
|
+
@field_groups ||= {}
|
128
|
+
end
|
129
|
+
|
51
130
|
# Sets or retrieves the unique identifier field for the class.
|
52
131
|
#
|
53
132
|
# This method defines or returns the field or method that contains the unique
|
@@ -90,30 +169,9 @@ module Familia
|
|
90
169
|
# - :skip - skip definition if method exists
|
91
170
|
# - :warn - warn but proceed (may overwrite)
|
92
171
|
# - :ignore - proceed silently (may overwrite)
|
93
|
-
#
|
94
|
-
|
95
|
-
|
96
|
-
# - :transient - field is not persisted
|
97
|
-
# - Others, depending on features available
|
98
|
-
#
|
99
|
-
def field(name, as: name, fast_method: :"#{name}!", on_conflict: :raise, category: nil)
|
100
|
-
# Use field type system for consistency
|
101
|
-
require_relative '../../field_type'
|
102
|
-
|
103
|
-
# Create appropriate field type based on category
|
104
|
-
field_type = if category == :transient
|
105
|
-
require_relative '../../features/transient_fields/transient_field_type'
|
106
|
-
TransientFieldType.new(name, as: as, fast_method: false, on_conflict: on_conflict)
|
107
|
-
else
|
108
|
-
# For regular fields and other categories, create custom field type with category override
|
109
|
-
custom_field_type = Class.new(FieldType) do
|
110
|
-
define_method :category do
|
111
|
-
category || :field
|
112
|
-
end
|
113
|
-
end
|
114
|
-
custom_field_type.new(name, as: as, fast_method: fast_method, on_conflict: on_conflict)
|
115
|
-
end
|
116
|
-
|
172
|
+
#
|
173
|
+
def field(name, as: name, fast_method: :"#{name}!", on_conflict: :raise)
|
174
|
+
field_type = FieldType.new(name, as: as, fast_method: fast_method, on_conflict: on_conflict)
|
117
175
|
register_field_type(field_type)
|
118
176
|
end
|
119
177
|
|
@@ -123,8 +181,8 @@ module Familia
|
|
123
181
|
# @param blk [Proc] a block that returns the suffix (optional).
|
124
182
|
# @return [String, Symbol] the current suffix or Familia.default_suffix if none is set.
|
125
183
|
#
|
126
|
-
def suffix(
|
127
|
-
@suffix =
|
184
|
+
def suffix(val = nil, &blk)
|
185
|
+
@suffix = val || blk if val || !blk.nil?
|
128
186
|
@suffix || Familia.default_suffix
|
129
187
|
end
|
130
188
|
|
@@ -137,8 +195,8 @@ module Familia
|
|
137
195
|
# which typically occurs with anonymous classes that haven't had their prefix
|
138
196
|
# explicitly set.
|
139
197
|
#
|
140
|
-
def prefix(
|
141
|
-
@prefix =
|
198
|
+
def prefix(val = nil)
|
199
|
+
@prefix = val if val
|
142
200
|
@prefix || begin
|
143
201
|
if name.nil?
|
144
202
|
raise Problem, 'Cannot generate prefix for anonymous class. ' \
|
@@ -148,9 +206,9 @@ module Familia
|
|
148
206
|
end
|
149
207
|
end
|
150
208
|
|
151
|
-
def logical_database(
|
152
|
-
Familia.trace :LOGICAL_DATABASE_DEF, "instvar:#{@logical_database}",
|
153
|
-
@logical_database =
|
209
|
+
def logical_database(num = nil)
|
210
|
+
Familia.trace :LOGICAL_DATABASE_DEF, "instvar:#{@logical_database}", num if Familia.debug?
|
211
|
+
@logical_database = num unless num.nil?
|
154
212
|
@logical_database || parent&.logical_database
|
155
213
|
end
|
156
214
|
|
@@ -175,14 +233,6 @@ module Familia
|
|
175
233
|
@has_related_fields ||= false
|
176
234
|
end
|
177
235
|
|
178
|
-
def dump_method
|
179
|
-
@dump_method || :to_json # Familia.dump_method
|
180
|
-
end
|
181
|
-
|
182
|
-
def load_method
|
183
|
-
@load_method || :from_json # Familia.load_method
|
184
|
-
end
|
185
|
-
|
186
236
|
# Storage for field type instances
|
187
237
|
def field_types
|
188
238
|
@field_types ||= {}
|
@@ -221,6 +271,12 @@ module Familia
|
|
221
271
|
# Complete the registration after installation. If we do this beforehand
|
222
272
|
# we can run into issues where it looks like it's already installed.
|
223
273
|
field_types[field_type.name] = field_type
|
274
|
+
|
275
|
+
# Add to current field group if one is active
|
276
|
+
if @current_field_group
|
277
|
+
@field_groups[@current_field_group] << field_type.name
|
278
|
+
end
|
279
|
+
|
224
280
|
# Freeze the field_type to ensure immutability (maintains Data class heritage)
|
225
281
|
field_type.freeze
|
226
282
|
end
|
@@ -303,16 +359,6 @@ module Familia
|
|
303
359
|
@feature_options[feature_name.to_sym]
|
304
360
|
end
|
305
361
|
|
306
|
-
# Create and register a transient field type
|
307
|
-
#
|
308
|
-
# @param name [Symbol] The field name
|
309
|
-
#
|
310
|
-
def transient_field(name, **)
|
311
|
-
require_relative '../../features/transient_fields/transient_field_type'
|
312
|
-
field_type = TransientFieldType.new(name, **, fast_method: false)
|
313
|
-
register_field_type(field_type)
|
314
|
-
end
|
315
|
-
|
316
362
|
private
|
317
363
|
|
318
364
|
# Hook to detect silent overwrites and handle conflicts
|
@@ -429,7 +475,7 @@ module Familia
|
|
429
475
|
|
430
476
|
# Convert the provided value to a format suitable for Database storage.
|
431
477
|
prepared = serialize_value(val)
|
432
|
-
Familia.ld "[
|
478
|
+
Familia.ld "[define_fast_writer_method] #{fast_method_name} val: #{val.class} prepared: #{prepared.class}"
|
433
479
|
|
434
480
|
# Use the existing accessor method to set the attribute value.
|
435
481
|
send :"#{method_name}=", val
|
@@ -1,6 +1,4 @@
|
|
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
|
@@ -127,15 +125,15 @@ module Familia
|
|
127
125
|
# User.find_by_key("user:123") # Returns a User instance if it exists,
|
128
126
|
# nil otherwise
|
129
127
|
#
|
130
|
-
def
|
128
|
+
def find_by_dbkey(objkey)
|
131
129
|
raise ArgumentError, 'Empty key' if objkey.to_s.empty?
|
132
130
|
|
133
131
|
# We use a lower-level method here b/c we're working with the
|
134
132
|
# full key and not just the identifier.
|
135
133
|
does_exist = dbclient.exists(objkey).positive?
|
136
134
|
|
137
|
-
Familia.ld "[
|
138
|
-
Familia.trace :
|
135
|
+
Familia.ld "[find_by_key] #{self} from key #{objkey} (exists: #{does_exist})"
|
136
|
+
Familia.trace :FIND_BY_DBKEY_KEY, nil, objkey
|
139
137
|
|
140
138
|
# This is the reason for calling exists first. We want to definitively
|
141
139
|
# and without any ambiguity know if the object exists in the database. If it
|
@@ -145,11 +143,16 @@ module Familia
|
|
145
143
|
return unless does_exist
|
146
144
|
|
147
145
|
obj = dbclient.hgetall(objkey) # horreum objects are persisted as database hashes
|
148
|
-
Familia.trace :
|
149
|
-
|
150
|
-
|
146
|
+
Familia.trace :FIND_BY_DBKEY_INSPECT, nil, "#{objkey}: #{obj.inspect}"
|
147
|
+
|
148
|
+
# Create instance and deserialize fields using existing helper method
|
149
|
+
# This avoids duplicating deserialization logic and keeps field-by-field processing
|
150
|
+
instance = allocate
|
151
|
+
instance.send(:initialize_relatives)
|
152
|
+
instance.send(:initialize_with_keyword_args_deserialize_value, **obj)
|
153
|
+
instance
|
151
154
|
end
|
152
|
-
alias
|
155
|
+
alias find_by_key find_by_dbkey
|
153
156
|
|
154
157
|
# Retrieves and instantiates an object from Database using its identifier.
|
155
158
|
#
|
@@ -170,19 +173,19 @@ module Familia
|
|
170
173
|
# @example
|
171
174
|
# User.find_by_id(123) # Equivalent to User.find_by_key("user:123:object")
|
172
175
|
#
|
173
|
-
def
|
176
|
+
def find_by_identifier(identifier, suffix = nil)
|
174
177
|
suffix ||= self.suffix
|
175
178
|
return nil if identifier.to_s.empty?
|
176
179
|
|
177
180
|
objkey = dbkey(identifier, suffix)
|
178
181
|
|
179
|
-
Familia.ld "[
|
182
|
+
Familia.ld "[find_by_id] #{self} from key #{objkey})"
|
180
183
|
Familia.trace :FIND_BY_ID, nil, objkey if Familia.debug?
|
181
|
-
|
184
|
+
find_by_dbkey objkey
|
182
185
|
end
|
186
|
+
alias find_by_id find_by_identifier
|
183
187
|
alias find find_by_id
|
184
|
-
alias load find_by_id
|
185
|
-
alias from_identifier find_by_id # deprecated
|
188
|
+
alias load find_by_id
|
186
189
|
|
187
190
|
# Checks if an object with the given identifier exists in the database.
|
188
191
|
#
|