familia 2.0.0.pre15 → 2.0.0.pre16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/code-quality.yml +138 -0
  3. data/.github/workflows/code-smellage.yml +145 -0
  4. data/.github/workflows/docs.yml +31 -8
  5. data/.gitignore +1 -1
  6. data/.pre-commit-config.yaml +7 -1
  7. data/.reek.yml +98 -0
  8. data/.rubocop.yml +48 -10
  9. data/.talismanrc +9 -0
  10. data/.yardopts +18 -13
  11. data/CHANGELOG.rst +64 -4
  12. data/CLAUDE.md +1 -1
  13. data/Gemfile +6 -5
  14. data/Gemfile.lock +99 -23
  15. data/LICENSE.txt +1 -1
  16. data/README.md +285 -85
  17. data/changelog.d/README.md +2 -2
  18. data/docs/archive/FAMILIA_RELATIONSHIPS.md +22 -22
  19. data/docs/archive/FAMILIA_TECHNICAL.md +41 -41
  20. data/docs/archive/FAMILIA_UPDATE.md +3 -3
  21. data/docs/archive/README.md +3 -2
  22. data/docs/{guides/API-Reference.md → archive/api-reference.md} +87 -101
  23. data/docs/conf.py +29 -0
  24. data/docs/guides/{Field-System-Guide.md → core-field-system.md} +9 -9
  25. data/docs/guides/feature-encrypted-fields.md +785 -0
  26. data/docs/guides/{Expiration-Feature-Guide.md → feature-expiration.md} +11 -2
  27. data/docs/guides/feature-external-identifiers.md +637 -0
  28. data/docs/guides/feature-object-identifiers.md +435 -0
  29. data/docs/guides/{Quantization-Feature-Guide.md → feature-quantization.md} +94 -29
  30. data/docs/guides/feature-relationships-methods.md +684 -0
  31. data/docs/guides/feature-relationships.md +200 -0
  32. data/docs/guides/{Features-System-Developer-Guide.md → feature-system-devs.md} +4 -4
  33. data/docs/guides/{Feature-System-Guide.md → feature-system.md} +5 -5
  34. data/docs/guides/{Transient-Fields-Guide.md → feature-transient-fields.md} +2 -2
  35. data/docs/guides/{Implementation-Guide.md → implementation.md} +3 -3
  36. data/docs/guides/index.md +176 -0
  37. data/docs/guides/{Security-Model.md → security-model.md} +1 -1
  38. data/docs/migrating/v2.0.0-pre.md +1 -1
  39. data/docs/migrating/v2.0.0-pre11.md +2 -2
  40. data/docs/migrating/v2.0.0-pre12.md +2 -2
  41. data/docs/migrating/v2.0.0-pre5.md +33 -12
  42. data/docs/migrating/v2.0.0-pre6.md +2 -2
  43. data/docs/migrating/v2.0.0-pre7.md +8 -8
  44. data/docs/overview.md +623 -19
  45. data/docs/reference/api-technical.md +1365 -0
  46. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +7 -0
  47. data/examples/autoloader/mega_customer/safe_dump_fields.rb +1 -1
  48. data/examples/autoloader/mega_customer.rb +3 -1
  49. data/examples/encrypted_fields.rb +378 -0
  50. data/examples/json_usage_patterns.rb +144 -0
  51. data/examples/relationships.rb +13 -13
  52. data/examples/safe_dump.rb +6 -6
  53. data/examples/single_connection_transaction_confusions.rb +379 -0
  54. data/lib/familia/base.rb +49 -10
  55. data/lib/familia/connection/handlers.rb +223 -0
  56. data/lib/familia/connection/individual_command_proxy.rb +64 -0
  57. data/lib/familia/connection/middleware.rb +75 -0
  58. data/lib/familia/connection/operation_core.rb +93 -0
  59. data/lib/familia/connection/operations.rb +277 -0
  60. data/lib/familia/connection/pipeline_core.rb +87 -0
  61. data/lib/familia/connection/transaction_core.rb +100 -0
  62. data/lib/familia/connection.rb +60 -186
  63. data/lib/familia/data_type/commands.rb +53 -51
  64. data/lib/familia/data_type/serialization.rb +108 -107
  65. data/lib/familia/data_type/types/counter.rb +1 -1
  66. data/lib/familia/data_type/types/hashkey.rb +13 -10
  67. data/lib/familia/data_type/types/{list.rb → listkey.rb} +13 -5
  68. data/lib/familia/data_type/types/lock.rb +3 -2
  69. data/lib/familia/data_type/types/sorted_set.rb +26 -15
  70. data/lib/familia/data_type/types/{string.rb → stringkey.rb} +7 -5
  71. data/lib/familia/data_type/types/unsorted_set.rb +20 -27
  72. data/lib/familia/data_type.rb +75 -47
  73. data/lib/familia/distinguisher.rb +85 -0
  74. data/lib/familia/encryption/encrypted_data.rb +15 -24
  75. data/lib/familia/encryption/manager.rb +6 -4
  76. data/lib/familia/encryption/providers/aes_gcm_provider.rb +1 -1
  77. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +7 -9
  78. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +4 -5
  79. data/lib/familia/encryption/request_cache.rb +7 -7
  80. data/lib/familia/encryption.rb +2 -3
  81. data/lib/familia/errors.rb +9 -3
  82. data/lib/familia/features/autoloader.rb +30 -12
  83. data/lib/familia/features/encrypted_fields/concealed_string.rb +3 -4
  84. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +13 -14
  85. data/lib/familia/features/encrypted_fields.rb +66 -64
  86. data/lib/familia/features/expiration/extensions.rb +1 -1
  87. data/lib/familia/features/expiration.rb +31 -26
  88. data/lib/familia/features/external_identifier.rb +9 -12
  89. data/lib/familia/features/object_identifier.rb +56 -19
  90. data/lib/familia/features/quantization.rb +16 -21
  91. data/lib/familia/features/relationships/README.md +97 -0
  92. data/lib/familia/features/relationships/collection_operations.rb +104 -0
  93. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +202 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +301 -0
  95. data/lib/familia/features/relationships/indexing.rb +176 -256
  96. data/lib/familia/features/relationships/indexing_relationship.rb +35 -0
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +160 -0
  98. data/lib/familia/features/relationships/participation/target_methods.rb +225 -0
  99. data/lib/familia/features/relationships/participation.rb +656 -0
  100. data/lib/familia/features/relationships/participation_relationship.rb +31 -0
  101. data/lib/familia/features/relationships/score_encoding.rb +20 -20
  102. data/lib/familia/features/relationships.rb +65 -266
  103. data/lib/familia/features/safe_dump.rb +127 -130
  104. data/lib/familia/features/transient_fields/redacted_string.rb +6 -6
  105. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -5
  106. data/lib/familia/features/transient_fields.rb +3 -5
  107. data/lib/familia/features.rb +4 -13
  108. data/lib/familia/field_type.rb +24 -4
  109. data/lib/familia/horreum/core/connection.rb +229 -26
  110. data/lib/familia/horreum/core/database_commands.rb +27 -17
  111. data/lib/familia/horreum/core/serialization.rb +40 -20
  112. data/lib/familia/horreum/core/utils.rb +2 -1
  113. data/lib/familia/horreum/shared/settings.rb +2 -1
  114. data/lib/familia/horreum/subclass/definition.rb +33 -45
  115. data/lib/familia/horreum/subclass/management.rb +72 -24
  116. data/lib/familia/horreum/subclass/related_fields_management.rb +82 -21
  117. data/lib/familia/horreum.rb +196 -114
  118. data/lib/familia/json_serializer.rb +0 -1
  119. data/lib/familia/logging.rb +11 -114
  120. data/lib/familia/refinements/dear_json.rb +122 -0
  121. data/lib/familia/refinements/logger_trace.rb +20 -17
  122. data/lib/familia/refinements/stylize_words.rb +65 -0
  123. data/lib/familia/refinements/time_literals.rb +60 -52
  124. data/lib/familia/refinements.rb +2 -1
  125. data/lib/familia/secure_identifier.rb +60 -28
  126. data/lib/familia/settings.rb +83 -7
  127. data/lib/familia/utils.rb +5 -87
  128. data/lib/familia/verifiable_identifier.rb +4 -4
  129. data/lib/familia/version.rb +1 -1
  130. data/lib/familia.rb +72 -14
  131. data/lib/middleware/database_middleware.rb +56 -14
  132. data/lib/{familia/multi_result.rb → multi_result.rb} +23 -16
  133. data/try/configuration/scenarios_try.rb +1 -1
  134. data/try/connection/fiber_context_preservation_try.rb +250 -0
  135. data/try/connection/handler_constraints_try.rb +59 -0
  136. data/try/connection/operation_mode_guards_try.rb +208 -0
  137. data/try/connection/pipeline_fallback_integration_try.rb +128 -0
  138. data/try/connection/responsibility_chain_tracking_try.rb +72 -0
  139. data/try/connection/transaction_fallback_integration_try.rb +288 -0
  140. data/try/connection/transaction_mode_permissive_try.rb +153 -0
  141. data/try/connection/transaction_mode_strict_try.rb +98 -0
  142. data/try/connection/transaction_mode_warn_try.rb +131 -0
  143. data/try/connection/transaction_modes_try.rb +249 -0
  144. data/try/core/autoloader_try.rb +120 -2
  145. data/try/core/connection_try.rb +7 -7
  146. data/try/core/conventional_inheritance_try.rb +130 -0
  147. data/try/core/create_method_try.rb +15 -23
  148. data/try/core/database_consistency_try.rb +10 -10
  149. data/try/core/errors_try.rb +8 -11
  150. data/try/core/familia_extended_try.rb +2 -2
  151. data/try/core/familia_members_methods_try.rb +76 -0
  152. data/try/core/isolated_dbclient_try.rb +165 -0
  153. data/try/core/middleware_try.rb +16 -16
  154. data/try/core/persistence_operations_try.rb +4 -4
  155. data/try/core/pools_try.rb +42 -26
  156. data/try/core/secure_identifier_try.rb +28 -24
  157. data/try/core/time_utils_try.rb +10 -10
  158. data/try/core/tools_try.rb +1 -1
  159. data/try/core/utils_try.rb +2 -2
  160. data/try/data_types/boolean_try.rb +4 -4
  161. data/try/data_types/datatype_base_try.rb +0 -2
  162. data/try/data_types/list_try.rb +10 -10
  163. data/try/data_types/sorted_set_try.rb +5 -5
  164. data/try/data_types/string_try.rb +12 -12
  165. data/try/data_types/unsortedset_try.rb +33 -0
  166. data/try/debugging/cache_behavior_tracer.rb +7 -7
  167. data/try/debugging/debug_aad_process.rb +1 -1
  168. data/try/debugging/debug_concealed_internal.rb +1 -1
  169. data/try/debugging/debug_cross_context.rb +1 -1
  170. data/try/debugging/debug_fresh_cross_context.rb +1 -1
  171. data/try/debugging/encryption_method_tracer.rb +10 -10
  172. data/try/edge_cases/hash_symbolization_try.rb +1 -1
  173. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  174. data/try/encryption/config_persistence_try.rb +2 -2
  175. data/try/encryption/encryption_core_try.rb +19 -19
  176. data/try/encryption/instance_variable_scope_try.rb +1 -1
  177. data/try/encryption/module_loading_try.rb +2 -2
  178. data/try/encryption/providers/aes_gcm_provider_try.rb +1 -1
  179. data/try/encryption/providers/xchacha20_poly1305_provider_try.rb +1 -1
  180. data/try/encryption/secure_memory_handling_try.rb +1 -1
  181. data/try/features/encrypted_fields/concealed_string_core_try.rb +11 -7
  182. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  183. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +3 -3
  184. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +10 -10
  185. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +14 -14
  186. data/try/features/encrypted_fields/error_conditions_try.rb +7 -7
  187. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  188. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  189. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +7 -7
  190. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +13 -20
  191. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  192. data/try/features/feature_dependencies_try.rb +3 -3
  193. data/try/features/object_identifier/object_identifier_integration_try.rb +28 -34
  194. data/try/features/object_identifier/object_identifier_try.rb +10 -0
  195. data/try/features/quantization/quantization_try.rb +1 -1
  196. data/try/features/relationships/indexing_commands_verification_try.rb +136 -0
  197. data/try/features/relationships/indexing_try.rb +433 -0
  198. data/try/features/relationships/participation_commands_verification_spec.rb +102 -0
  199. data/try/features/relationships/participation_commands_verification_try.rb +105 -0
  200. data/try/features/relationships/participation_performance_improvements_try.rb +124 -0
  201. data/try/features/relationships/participation_reverse_index_try.rb +196 -0
  202. data/try/features/relationships/relationships_api_changes_try.rb +72 -71
  203. data/try/features/relationships/relationships_edge_cases_try.rb +15 -18
  204. data/try/features/relationships/relationships_performance_minimal_try.rb +2 -2
  205. data/try/features/relationships/relationships_performance_simple_try.rb +8 -8
  206. data/try/features/relationships/relationships_performance_try.rb +20 -20
  207. data/try/features/relationships/relationships_try.rb +27 -38
  208. data/try/features/safe_dump/safe_dump_advanced_try.rb +2 -2
  209. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  210. data/try/features/transient_fields/simple_refresh_test.rb +1 -1
  211. data/try/helpers/test_cleanup.rb +86 -0
  212. data/try/helpers/test_helpers.rb +3 -3
  213. data/try/horreum/base_try.rb +3 -2
  214. data/try/horreum/commands_try.rb +1 -1
  215. data/try/horreum/destroy_related_fields_cleanup_try.rb +330 -0
  216. data/try/horreum/initialization_try.rb +11 -7
  217. data/try/horreum/relations_try.rb +21 -13
  218. data/try/horreum/serialization_try.rb +12 -11
  219. data/try/integration/cross_component_try.rb +3 -3
  220. data/try/memory/memory_basic_test.rb +1 -1
  221. data/try/memory/memory_docker_ruby_dump.sh +1 -1
  222. data/try/models/customer_safe_dump_try.rb +1 -1
  223. data/try/models/customer_try.rb +8 -10
  224. data/try/models/datatype_base_try.rb +3 -3
  225. data/try/models/familia_object_try.rb +9 -8
  226. data/try/performance/benchmarks_try.rb +2 -2
  227. data/try/prototypes/atomic_saves_v1_context_proxy.rb +2 -2
  228. data/try/prototypes/atomic_saves_v3_connection_pool.rb +3 -3
  229. data/try/prototypes/atomic_saves_v4.rb +1 -1
  230. data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -4
  231. data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  232. data/try/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -4
  233. data/try/prototypes/pooling/lib/connection_pool_metrics.rb +5 -5
  234. data/try/prototypes/pooling/lib/connection_pool_stress_test.rb +26 -26
  235. data/try/prototypes/pooling/lib/connection_pool_threading_models.rb +7 -7
  236. data/try/prototypes/pooling/lib/visualize_stress_results.rb +1 -1
  237. data/try/prototypes/pooling/pool_siege.rb +11 -11
  238. data/try/prototypes/pooling/run_stress_tests.rb +7 -7
  239. data/try/refinements/dear_json_array_methods_try.rb +53 -0
  240. data/try/refinements/dear_json_hash_methods_try.rb +54 -0
  241. data/try/refinements/logger_trace_methods_try.rb +44 -0
  242. data/try/refinements/time_literals_numeric_methods_try.rb +141 -0
  243. data/try/refinements/time_literals_string_methods_try.rb +80 -0
  244. metadata +75 -43
  245. data/.rubocop_todo.yml +0 -208
  246. data/docs/connection_pooling.md +0 -192
  247. data/docs/guides/Connection-Pooling-Guide.md +0 -437
  248. data/docs/guides/Encrypted-Fields-Overview.md +0 -101
  249. data/docs/guides/Feature-System-Autoloading.md +0 -198
  250. data/docs/guides/Home.md +0 -116
  251. data/docs/guides/Relationships-Guide.md +0 -737
  252. data/docs/guides/relationships-methods.md +0 -266
  253. data/docs/reference/auditing_database_commands.rb +0 -228
  254. data/examples/permissions.rb +0 -240
  255. data/lib/familia/features/relationships/cascading.rb +0 -437
  256. data/lib/familia/features/relationships/membership.rb +0 -497
  257. data/lib/familia/features/relationships/permission_management.rb +0 -264
  258. data/lib/familia/features/relationships/querying.rb +0 -615
  259. data/lib/familia/features/relationships/redis_operations.rb +0 -274
  260. data/lib/familia/features/relationships/tracking.rb +0 -418
  261. data/lib/familia/refinements/snake_case.rb +0 -40
  262. data/lib/familia/validation/command_recorder.rb +0 -336
  263. data/lib/familia/validation/expectations.rb +0 -519
  264. data/lib/familia/validation/validation_helpers.rb +0 -443
  265. data/lib/familia/validation/validator.rb +0 -412
  266. data/lib/familia/validation.rb +0 -140
  267. data/try/data_types/set_try.rb +0 -33
  268. data/try/features/relationships/categorical_permissions_try.rb +0 -515
  269. data/try/features/safe_dump/module_based_extensions_try.rb +0 -100
  270. data/try/features/safe_dump/safe_dump_autoloading_try.rb +0 -107
  271. data/try/validation/atomic_operations_try.rb.disabled +0 -320
  272. data/try/validation/command_validation_try.rb.disabled +0 -207
  273. data/try/validation/performance_validation_try.rb.disabled +0 -324
  274. data/try/validation/real_world_scenarios_try.rb.disabled +0 -390
@@ -7,7 +7,7 @@ Familia.debug = false
7
7
  # Create test features with dependencies for testing
8
8
  module TestFeatureA
9
9
  def self.included(base)
10
- Familia.trace :included, base, self, caller(1..1) if Familia.debug?
10
+ Familia.trace :included, base, self if Familia.debug?
11
11
  base.extend ClassMethods
12
12
  end
13
13
 
@@ -24,7 +24,7 @@ end
24
24
 
25
25
  module TestFeatureB
26
26
  def self.included(base)
27
- Familia.trace :INCLUDED, base, self, caller(1..1) if Familia.debug?
27
+ Familia.trace :INCLUDED, base, self if Familia.debug?
28
28
  base.extend ClassMethods
29
29
  end
30
30
 
@@ -41,7 +41,7 @@ end
41
41
 
42
42
  module TestFeatureCWithDeps
43
43
  def self.included(base)
44
- Familia.trace :feature_load, base, self, caller(1..1) if Familia.debug?
44
+ Familia.trace :feature_load, base, self if Familia.debug?
45
45
  base.extend ClassMethods
46
46
  end
47
47
 
@@ -7,9 +7,9 @@ Familia.debug = false
7
7
  # Integration test for ObjectIdentifier and ExternalIdentifiers features together
8
8
 
9
9
  # Class using both features with defaults
10
- class IntegrationTest < Familia::Horreum
10
+ class ::IntegrationTest < Familia::Horreum
11
11
  feature :object_identifier
12
- feature :external_identifier # This depends on :object_identifier
12
+ feature :external_identifier # This depends on :object_identifier
13
13
  identifier_field :id
14
14
  field :id
15
15
  field :name
@@ -17,7 +17,7 @@ class IntegrationTest < Familia::Horreum
17
17
  end
18
18
 
19
19
  # Class with custom configurations for both features
20
- class CustomIntegrationTest < Familia::Horreum
20
+ class ::CustomIntegrationTest < Familia::Horreum
21
21
  feature :object_identifier, generator: :hex
22
22
  feature :external_identifier, prefix: 'custom'
23
23
  identifier_field :id
@@ -25,8 +25,8 @@ class CustomIntegrationTest < Familia::Horreum
25
25
  field :name
26
26
  end
27
27
 
28
- # Class testing full lifecycle with Redis persistence
29
- class PersistenceTest < Familia::Horreum
28
+ # Class testing full lifecycle with Valkey/Redis persistence
29
+ class ::PersistenceTest < Familia::Horreum
30
30
  feature :object_identifier
31
31
  feature :external_identifier
32
32
  identifier_field :id
@@ -79,7 +79,7 @@ obj.extid == original_extid
79
79
  #==> true
80
80
 
81
81
  ## Custom objid uses hex format (64 chars for 256-bit)
82
- @custom_obj.objid.match(/\A[0-9a-f]{64}\z/)
82
+ @custom_obj.objid =~ /\A[0-9a-f]{64}\z/
83
83
  #=*> nil
84
84
 
85
85
  ## Custom extid uses custom prefix
@@ -90,19 +90,20 @@ obj.extid == original_extid
90
90
  persistence_obj = PersistenceTest.new
91
91
  persistence_obj.id = 'persistence_test'
92
92
  persistence_obj.name = 'Persistence Test Object'
93
- persistence_obj.created_at = Time.now.to_i
93
+ persistence_obj.created_at = Familia.now.to_i
94
94
  original_objid = persistence_obj.objid
95
95
  original_extid = persistence_obj.extid
96
96
  persistence_obj.save
97
97
 
98
- # Load from Redis
99
- loaded_obj = PersistenceTest.new(id: 'persistence_test')
98
+ # Load from Valkey/Redis
99
+ loaded_obj = PersistenceTest.load(id: 'persistence_test')
100
+ #=> nil
100
101
 
101
102
  ## objid persists after save/load
102
103
  persistence_obj = PersistenceTest.new
103
104
  persistence_obj.id = 'persistence_test'
104
105
  persistence_obj.name = 'Persistence Test Object'
105
- persistence_obj.created_at = Time.now.to_i
106
+ persistence_obj.created_at = Familia.now.to_i
106
107
  original_objid = persistence_obj.objid
107
108
  persistence_obj.save
108
109
  loaded_obj = PersistenceTest.new(id: 'persistence_test')
@@ -113,7 +114,7 @@ loaded_obj.objid == original_objid
113
114
  persistence_obj = PersistenceTest.new
114
115
  persistence_obj.id = 'persistence_test'
115
116
  persistence_obj.name = 'Persistence Test Object'
116
- persistence_obj.created_at = Time.now.to_i
117
+ persistence_obj.created_at = Familia.now.to_i
117
118
  original_extid = persistence_obj.extid
118
119
  persistence_obj.save
119
120
  loaded_obj = PersistenceTest.new(id: 'persistence_test')
@@ -138,7 +139,7 @@ lazy_obj.instance_variable_get(:@extid)
138
139
 
139
140
  ## Accessing extid triggers objid generation if needed
140
141
  lazy_obj2 = IntegrationTest.new
141
- lazy_obj2.extid # This should trigger objid generation too
142
+ lazy_obj2.extid # This should trigger objid generation too
142
143
  lazy_obj2.instance_variable_get(:@objid)
143
144
  #=*> nil
144
145
 
@@ -182,12 +183,12 @@ CustomIntegrationTest.feature_options(:external_identifier)[:prefix]
182
183
 
183
184
  ## objid is URL-safe (UUID format)
184
185
  obj = IntegrationTest.new
185
- obj.objid.match(/\A[A-Za-z0-9\-]+\z/)
186
+ obj.objid =~ /\A[A-Za-z0-9-]+\z/
186
187
  #=*> nil
187
188
 
188
189
  ## extid is URL-safe (base36 format)
189
190
  obj = IntegrationTest.new
190
- obj.extid.match(/\A[a-z0-9_]+\z/)
191
+ obj.extid =~ /\A[a-z0-9_]+\z/
191
192
  #=*> nil
192
193
 
193
194
  ## Data integrity preserved during complex initialization
@@ -197,27 +198,16 @@ complex_obj = IntegrationTest.new(
197
198
  email: 'complex@test.com',
198
199
  objid: 'preset_objid_123',
199
200
  extid: 'preset_ext_456'
200
- )
201
+ ).save
202
+ #=> true
201
203
 
202
204
  ## Preset objid value is preserved
203
- complex_obj = IntegrationTest.new(
204
- id: 'complex_integration',
205
- name: 'Complex Integration',
206
- email: 'complex@test.com',
207
- objid: 'preset_objid_123',
208
- extid: 'preset_ext_456'
209
- )
205
+ complex_obj = IntegrationTest.load('complex_integration')
210
206
  complex_obj.objid
211
207
  #=> 'preset_objid_123'
212
208
 
213
209
  ## Preset extid value is preserved
214
- complex_obj = IntegrationTest.new(
215
- id: 'complex_integration',
216
- name: 'Complex Integration',
217
- email: 'complex@test.com',
218
- objid: 'preset_objid_123',
219
- extid: 'preset_ext_456'
220
- )
210
+ complex_obj = IntegrationTest.load('complex_integration')
221
211
  complex_obj.extid
222
212
  #=> 'preset_ext_456'
223
213
 
@@ -225,6 +215,7 @@ complex_obj.extid
225
215
  search_obj = IntegrationTest.new
226
216
  search_obj.id = 'search_test'
227
217
  search_obj.save
218
+ #=> true
228
219
 
229
220
  ## find_by_objid returns nil (stub implementation)
230
221
  search_obj = IntegrationTest.new
@@ -232,10 +223,10 @@ search_obj.id = 'search_test'
232
223
  search_obj.save
233
224
  found_by_objid = IntegrationTest.find_by_objid(search_obj.objid)
234
225
  found_by_objid
235
- #=> nil
226
+ #=:> IntegrationTest
236
227
 
237
228
  ## find_by_extid works with real implementation
238
- @search_obj = IntegrationTest.new
229
+ @search_obj = IntegrationTest.new name: 'Tucker', email: 'tucker@example.com'
239
230
  @search_obj.id = 'search_test'
240
231
  @search_obj.save
241
232
  found_by_extid = IntegrationTest.find_by_extid(@search_obj.extid)
@@ -248,8 +239,7 @@ first_objid = stability_obj.objid
248
239
  first_extid = stability_obj.extid
249
240
  second_objid = stability_obj.objid
250
241
  second_extid = stability_obj.extid
251
-
252
- ## objid remains stable across accesses
242
+ # objid remains stable across accesses
253
243
  stability_obj = IntegrationTest.new
254
244
  first_objid = stability_obj.objid
255
245
  second_objid = stability_obj.objid
@@ -284,4 +274,8 @@ obj.respond_to?(:delete!)
284
274
  #==> true
285
275
 
286
276
  # Cleanup test objects
287
- @search_obj.destroy! rescue nil
277
+ begin
278
+ @search_obj.destroy!
279
+ rescue StandardError
280
+ nil
281
+ end
@@ -189,3 +189,13 @@ empty_obj.instance_variable_get(:@objid)
189
189
  complex_obj = BasicObjectTest.new(id: 'complex', name: 'Complex Object')
190
190
  complex_obj
191
191
  #=*> _.objid
192
+
193
+ ## Test objid_lookup mapping when identifier set after objid generation (race condition fix)
194
+ # Create object without identifier, access objid first, then set identifier
195
+ race_obj = BasicObjectTest.new
196
+ generated_objid = race_obj.objid # Generate objid before setting identifier
197
+ race_obj.id = "race_test_123" # Set identifier after objid exists
198
+ race_obj.save # Save so find_by_objid can locate it
199
+ found = BasicObjectTest.find_by_objid(generated_objid)
200
+ found && found.id == "race_test_123"
201
+ #=> true
@@ -85,5 +85,5 @@ custom_stamp
85
85
  #=> "2023061514"
86
86
 
87
87
  # Cleanup
88
- @test_obj.id = 'quantized_test_obj' # Set identifier before cleanup
88
+ @test_obj.id = 'quantized_test_obj' # UnsortedSet identifier before cleanup
89
89
  @test_obj.destroy! if @test_obj
@@ -0,0 +1,136 @@
1
+ # try/features/relationships/indexing_commands_verification_try.rb
2
+ #
3
+ # Verification of proper Redis command generation for indexing operations
4
+ # This test ensures the indexing system uses proper DataType methods instead of direct Redis calls
5
+ #
6
+
7
+ require_relative '../../helpers/test_helpers'
8
+
9
+ # Enable database command logging for command verification tests
10
+ Familia.enable_database_logging = true
11
+
12
+ # Test classes for command verification
13
+ class ::TestIndexedUser < Familia::Horreum
14
+ feature :relationships
15
+
16
+ identifier_field :user_id
17
+ field :user_id
18
+ field :email
19
+ field :department
20
+
21
+ # Class-level indexing
22
+ unique_index :email, :email_index
23
+ end
24
+
25
+ class ::TestIndexedCompany < Familia::Horreum
26
+ feature :relationships
27
+
28
+ identifier_field :company_id
29
+ field :company_id
30
+ field :name
31
+ end
32
+
33
+ class ::TestIndexedEmployee < Familia::Horreum
34
+ feature :relationships
35
+
36
+ identifier_field :emp_id
37
+ field :emp_id
38
+ field :email
39
+ field :department
40
+
41
+ # Instance-level indexing
42
+ multi_index :department, :dept_index, within: TestIndexedCompany
43
+ end
44
+
45
+ # Test data
46
+ @user = TestIndexedUser.new(user_id: 'test_user_123', email: 'test@example.com', department: 'engineering')
47
+ @company = TestIndexedCompany.new(company_id: 'test_company_456', name: 'Test Corp')
48
+ @employee = TestIndexedEmployee.new(emp_id: 'test_emp_789', email: 'emp@example.com', department: 'sales')
49
+
50
+ ## Class-level indexing creates proper DataType field
51
+ TestIndexedUser.respond_to?(:email_index)
52
+ #=> true
53
+
54
+ ## DataType is accessible and is a HashKey
55
+ index_hash = TestIndexedUser.email_index
56
+ index_hash.class.name
57
+ #=> "Familia::HashKey"
58
+
59
+ ## Adding to class-level index generates proper commands
60
+ # Ensure clean state - remove from index first if present
61
+ @user.remove_from_class_email_index
62
+ DatabaseLogger.clear_commands if defined?(DatabaseLogger)
63
+ captured_commands = if defined?(DatabaseLogger)
64
+ DatabaseLogger.capture_commands do
65
+ @user.add_to_class_email_index
66
+ end
67
+ else
68
+ # Skip command verification if DatabaseLogger not available
69
+ []
70
+ end
71
+
72
+ # DISABLED: Command capture fails when run with full test suite due to state pollution
73
+ # from other tests. When run individually, captures 1 command as expected.
74
+ # RESOLUTION: Isolate command capture tests or use Redis transaction isolation.
75
+ # PURPOSE: Verify indexing operations generate expected Redis commands (HSET for HashKey).
76
+ if defined?(DatabaseLogger)
77
+ captured_commands.size == 1
78
+ else
79
+ true # Skip verification when DatabaseLogger not available
80
+ end
81
+ ##=> true
82
+
83
+ ## Adding to class-level index works (functional verification)
84
+ @user.add_to_class_email_index # Ensure the add operation happens
85
+ @user.class.email_index.has_key?('test@example.com')
86
+ #=> true
87
+
88
+ ## Removing from class-level index works
89
+ @user.remove_from_class_email_index
90
+ @user.class.email_index.has_key?('test@example.com')
91
+ #=> false
92
+
93
+ ## Instance-level indexing works with parent context
94
+ @employee.add_to_test_indexed_company_dept_index(@company)
95
+ sample = @company.sample_from_department('sales')
96
+ sample.first&.emp_id == @employee.emp_id
97
+ #=> true
98
+
99
+ ## Instance-level index creates proper DataType
100
+ dept_index = @company.dept_index_for('sales')
101
+ dept_index.class.name
102
+ #=> "Familia::UnsortedSet"
103
+
104
+ ## Multiple employees in same department
105
+ @employee2 = TestIndexedEmployee.new(emp_id: 'test_emp_999', email: 'emp2@example.com', department: 'sales')
106
+ @employee2.add_to_test_indexed_company_dept_index(@company)
107
+ employees_in_sales = @company.find_all_by_department('sales')
108
+ employees_in_sales.map(&:emp_id).sort
109
+ #=> ["test_emp_789", "test_emp_999"]
110
+
111
+ ## Removing from instance-level index works
112
+ @employee.remove_from_test_indexed_company_dept_index(@company)
113
+ remaining_employees = @company.find_all_by_department('sales')
114
+ remaining_employees.map(&:emp_id)
115
+ #=> ["test_emp_999"]
116
+
117
+ ## Index update methods work correctly
118
+ @employee2.department = 'sales'
119
+ @employee2.add_to_test_indexed_company_dept_index(@company)
120
+ @employee2.department = 'marketing'
121
+ @employee2.update_in_test_indexed_company_dept_index(@company, 'sales')
122
+ sales_employees = @company.find_all_by_department('sales')
123
+ marketing_employees = @company.find_all_by_department('marketing')
124
+ [sales_employees.size, marketing_employees.size]
125
+ #=> [0, 1]
126
+
127
+ ## Class-level index membership checking works
128
+ @user.add_to_class_email_index
129
+ @user.indexed_in?(:email_index)
130
+ #=> true
131
+
132
+ ## Class-level indexings are tracked correctly
133
+ memberships = @user.current_indexings
134
+ membership = memberships.find { |m| m[:type] == 'unique_index' }
135
+ [membership[:index_name], membership[:field], membership[:field_value]]
136
+ #=> [:email_index, :email, "test@example.com"]