familia 2.0.0.pre19 → 2.0.0.pre22

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 (370) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +4 -9
  3. data/.github/workflows/code-smells.yml +64 -3
  4. data/.pre-commit-config.yaml +8 -6
  5. data/.reek.yml +10 -9
  6. data/.rubocop.yml +4 -0
  7. data/.talismanrc +5 -1
  8. data/CHANGELOG.rst +220 -112
  9. data/CLAUDE.md +28 -1
  10. data/Gemfile +1 -1
  11. data/Gemfile.lock +20 -17
  12. data/bin/try +16 -0
  13. data/bin/tryouts +16 -0
  14. data/docs/1106-participates_in-bidirectional-solution.md +129 -0
  15. data/docs/guides/encryption.md +486 -0
  16. data/docs/guides/feature-encrypted-fields.md +123 -7
  17. data/docs/guides/feature-expiration.md +161 -117
  18. data/docs/guides/feature-external-identifiers.md +415 -443
  19. data/docs/guides/feature-object-identifiers.md +400 -269
  20. data/docs/guides/feature-quantization.md +120 -6
  21. data/docs/guides/feature-relationships-indexing.md +318 -0
  22. data/docs/guides/feature-relationships-methods.md +146 -604
  23. data/docs/guides/feature-relationships-participation.md +263 -0
  24. data/docs/guides/feature-relationships.md +118 -136
  25. data/docs/guides/feature-system-devs.md +176 -693
  26. data/docs/guides/feature-system.md +119 -6
  27. data/docs/guides/feature-transient-fields.md +81 -0
  28. data/docs/guides/field-system.md +778 -0
  29. data/docs/guides/index.md +32 -15
  30. data/docs/guides/logging.md +187 -0
  31. data/docs/guides/optimized-loading.md +674 -0
  32. data/docs/guides/thread-safety-monitoring.md +61 -0
  33. data/docs/guides/{time-utilities.md → time-literals.md} +12 -12
  34. data/docs/migrating/v2.0.0-pre22.md +241 -0
  35. data/docs/overview.md +7 -9
  36. data/docs/reference/api-technical.md +267 -320
  37. data/examples/autoloader/mega_customer/features/deprecated_fields.rb +2 -0
  38. data/examples/autoloader/mega_customer/safe_dump_fields.rb +2 -0
  39. data/examples/autoloader/mega_customer.rb +2 -0
  40. data/examples/datatype_standalone.rb +4 -3
  41. data/examples/encrypted_fields.rb +2 -1
  42. data/examples/json_usage_patterns.rb +2 -0
  43. data/examples/relationships.rb +3 -0
  44. data/examples/safe_dump.rb +2 -1
  45. data/examples/sampling_demo.rb +53 -0
  46. data/examples/single_connection_transaction_confusions.rb +2 -1
  47. data/familia.gemspec +2 -1
  48. data/lib/familia/base.rb +2 -0
  49. data/lib/familia/connection/behavior.rb +2 -0
  50. data/lib/familia/connection/handlers.rb +2 -0
  51. data/lib/familia/connection/individual_command_proxy.rb +2 -0
  52. data/lib/familia/connection/middleware.rb +34 -24
  53. data/lib/familia/connection/operation_core.rb +3 -2
  54. data/lib/familia/connection/operations.rb +2 -0
  55. data/lib/familia/connection/pipelined_core.rb +3 -3
  56. data/lib/familia/connection/transaction_core.rb +69 -2
  57. data/lib/familia/connection.rb +18 -3
  58. data/lib/familia/data_type/class_methods.rb +3 -1
  59. data/lib/familia/data_type/connection.rb +2 -0
  60. data/lib/familia/data_type/database_commands.rb +2 -0
  61. data/lib/familia/data_type/serialization.rb +79 -52
  62. data/lib/familia/data_type/settings.rb +2 -0
  63. data/lib/familia/data_type/types/counter.rb +2 -0
  64. data/lib/familia/data_type/types/hashkey.rb +7 -5
  65. data/lib/familia/data_type/types/listkey.rb +2 -0
  66. data/lib/familia/data_type/types/lock.rb +2 -0
  67. data/lib/familia/data_type/types/sorted_set.rb +7 -10
  68. data/lib/familia/data_type/types/stringkey.rb +24 -0
  69. data/lib/familia/data_type/types/unsorted_set.rb +2 -0
  70. data/lib/familia/data_type.rb +2 -0
  71. data/lib/familia/encryption/encrypted_data.rb +4 -2
  72. data/lib/familia/encryption/manager.rb +2 -0
  73. data/lib/familia/encryption/provider.rb +2 -0
  74. data/lib/familia/encryption/providers/aes_gcm_provider.rb +2 -0
  75. data/lib/familia/encryption/providers/secure_xchacha20_poly1305_provider.rb +2 -0
  76. data/lib/familia/encryption/providers/xchacha20_poly1305_provider.rb +2 -0
  77. data/lib/familia/encryption/registry.rb +2 -0
  78. data/lib/familia/encryption/request_cache.rb +2 -0
  79. data/lib/familia/encryption.rb +9 -2
  80. data/lib/familia/errors.rb +2 -0
  81. data/lib/familia/features/autoloader.rb +2 -0
  82. data/lib/familia/features/encrypted_fields/concealed_string.rb +2 -0
  83. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +4 -0
  84. data/lib/familia/features/encrypted_fields.rb +2 -2
  85. data/lib/familia/features/expiration/extensions.rb +3 -1
  86. data/lib/familia/features/expiration.rb +12 -4
  87. data/lib/familia/features/external_identifier.rb +62 -7
  88. data/lib/familia/features/object_identifier.rb +49 -0
  89. data/lib/familia/features/quantization.rb +3 -1
  90. data/lib/familia/features/relationships/README.md +3 -1
  91. data/lib/familia/features/relationships/collection_operations.rb +2 -0
  92. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +138 -9
  93. data/lib/familia/features/relationships/indexing/rebuild_strategies.rb +479 -0
  94. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +97 -21
  95. data/lib/familia/features/relationships/indexing.rb +3 -0
  96. data/lib/familia/features/relationships/indexing_relationship.rb +3 -1
  97. data/lib/familia/features/relationships/participation/participant_methods.rb +131 -14
  98. data/lib/familia/features/relationships/participation/rebuild_strategies.md +41 -0
  99. data/lib/familia/features/relationships/participation/target_methods.rb +6 -6
  100. data/lib/familia/features/relationships/participation.rb +155 -69
  101. data/lib/familia/features/relationships/participation_membership.rb +69 -0
  102. data/lib/familia/features/relationships/participation_relationship.rb +34 -6
  103. data/lib/familia/features/relationships/score_encoding.rb +2 -0
  104. data/lib/familia/features/relationships.rb +5 -3
  105. data/lib/familia/features/safe_dump.rb +2 -0
  106. data/lib/familia/features/transient_fields/redacted_string.rb +2 -0
  107. data/lib/familia/features/transient_fields/single_use_redacted_string.rb +2 -0
  108. data/lib/familia/features/transient_fields/transient_field_type.rb +5 -3
  109. data/lib/familia/features/transient_fields.rb +2 -0
  110. data/lib/familia/features.rb +2 -0
  111. data/lib/familia/field_type.rb +3 -1
  112. data/lib/familia/horreum/connection.rb +17 -1
  113. data/lib/familia/horreum/database_commands.rb +8 -1
  114. data/lib/familia/horreum/definition.rb +16 -6
  115. data/lib/familia/horreum/management.rb +353 -52
  116. data/lib/familia/horreum/persistence.rb +179 -108
  117. data/lib/familia/horreum/related_fields.rb +2 -0
  118. data/lib/familia/horreum/serialization.rb +23 -4
  119. data/lib/familia/horreum/settings.rb +2 -0
  120. data/lib/familia/horreum/utils.rb +2 -0
  121. data/lib/familia/horreum.rb +15 -1
  122. data/lib/familia/identifier_extractor.rb +3 -1
  123. data/lib/familia/instrumentation.rb +156 -0
  124. data/lib/familia/json_serializer.rb +2 -0
  125. data/lib/familia/logging.rb +92 -32
  126. data/lib/familia/refinements/dear_json.rb +2 -0
  127. data/lib/familia/refinements/stylize_words.rb +2 -14
  128. data/lib/familia/refinements/time_literals.rb +2 -0
  129. data/lib/familia/refinements.rb +2 -0
  130. data/lib/familia/secure_identifier.rb +10 -2
  131. data/lib/familia/settings.rb +2 -0
  132. data/lib/familia/thread_safety/instrumented_mutex.rb +166 -0
  133. data/lib/familia/thread_safety/monitor.rb +328 -0
  134. data/lib/familia/utils.rb +13 -0
  135. data/lib/familia/verifiable_identifier.rb +3 -1
  136. data/lib/familia/version.rb +3 -1
  137. data/lib/familia.rb +31 -4
  138. data/lib/middleware/database_command_counter.rb +152 -0
  139. data/lib/middleware/database_logger.rb +295 -170
  140. data/lib/multi_result.rb +61 -31
  141. data/try/edge_cases/empty_identifiers_try.rb +2 -0
  142. data/try/edge_cases/hash_symbolization_try.rb +2 -0
  143. data/try/edge_cases/json_serialization_try.rb +2 -0
  144. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +4 -0
  145. data/try/edge_cases/race_conditions_try.rb +4 -0
  146. data/try/edge_cases/reserved_keywords_try.rb +4 -0
  147. data/try/edge_cases/string_coercion_try.rb +2 -0
  148. data/try/edge_cases/ttl_side_effects_try.rb +4 -0
  149. data/try/features/count_any_edge_cases_try.rb +486 -0
  150. data/try/features/count_any_methods_try.rb +197 -0
  151. data/try/features/encrypted_fields/aad_protection_try.rb +4 -0
  152. data/try/features/encrypted_fields/concealed_string_core_try.rb +4 -0
  153. data/try/features/encrypted_fields/context_isolation_try.rb +4 -0
  154. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +33 -0
  155. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +4 -0
  156. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +4 -0
  157. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +4 -0
  158. data/try/features/encrypted_fields/error_conditions_try.rb +4 -0
  159. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +4 -0
  160. data/try/features/encrypted_fields/fresh_key_try.rb +4 -0
  161. data/try/features/encrypted_fields/key_rotation_try.rb +4 -0
  162. data/try/features/encrypted_fields/memory_security_try.rb +4 -0
  163. data/try/features/encrypted_fields/missing_current_key_version_try.rb +4 -0
  164. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +4 -0
  165. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +4 -0
  166. data/try/features/encrypted_fields/thread_safety_try.rb +4 -0
  167. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +4 -0
  168. data/try/features/encryption/config_persistence_try.rb +4 -0
  169. data/try/features/encryption/core_try.rb +4 -0
  170. data/try/features/encryption/instance_variable_scope_try.rb +4 -0
  171. data/try/features/encryption/module_loading_try.rb +4 -0
  172. data/try/features/encryption/providers/aes_gcm_provider_try.rb +4 -0
  173. data/try/features/encryption/providers/xchacha20_poly1305_provider_try.rb +4 -0
  174. data/try/features/encryption/roundtrip_validation_try.rb +4 -0
  175. data/try/features/encryption/secure_memory_handling_try.rb +4 -0
  176. data/try/features/expiration/expiration_try.rb +4 -0
  177. data/try/features/external_identifier/external_identifier_try.rb +305 -8
  178. data/try/features/feature_dependencies_try.rb +2 -0
  179. data/try/features/feature_improvements_try.rb +2 -0
  180. data/try/features/field_groups_try.rb +2 -0
  181. data/try/features/object_identifier/object_identifier_integration_try.rb +12 -9
  182. data/try/features/object_identifier/object_identifier_try.rb +140 -0
  183. data/try/features/quantization/quantization_try.rb +4 -0
  184. data/try/features/real_feature_integration_try.rb +2 -0
  185. data/try/features/relationships/indexing_commands_verification_try.rb +2 -0
  186. data/try/features/relationships/indexing_rebuild_try.rb +606 -0
  187. data/try/features/relationships/indexing_try.rb +2 -0
  188. data/try/features/relationships/participation_bidirectional_try.rb +242 -0
  189. data/try/features/relationships/participation_commands_verification_spec.rb +4 -0
  190. data/try/features/relationships/participation_commands_verification_try.rb +2 -0
  191. data/try/features/relationships/participation_performance_improvements_try.rb +11 -9
  192. data/try/features/relationships/participation_reverse_index_try.rb +15 -13
  193. data/try/features/relationships/participation_target_class_resolution_try.rb +209 -0
  194. data/try/features/relationships/participation_unresolved_target_try.rb +109 -0
  195. data/try/features/relationships/relationships_api_changes_try.rb +2 -0
  196. data/try/features/relationships/relationships_edge_cases_try.rb +4 -0
  197. data/try/features/relationships/relationships_performance_minimal_try.rb +4 -0
  198. data/try/features/relationships/relationships_performance_simple_try.rb +4 -0
  199. data/try/features/relationships/relationships_performance_try.rb +4 -0
  200. data/try/features/relationships/relationships_performance_working_try.rb +4 -0
  201. data/try/features/relationships/relationships_try.rb +6 -4
  202. data/try/features/safe_dump/safe_dump_advanced_try.rb +4 -0
  203. data/try/features/safe_dump/safe_dump_try.rb +4 -0
  204. data/try/features/transient_fields/redacted_string_try.rb +2 -0
  205. data/try/features/transient_fields/refresh_reset_try.rb +3 -0
  206. data/try/features/transient_fields/simple_refresh_test.rb +3 -0
  207. data/try/features/transient_fields/single_use_redacted_string_try.rb +2 -0
  208. data/try/features/transient_fields/transient_fields_core_try.rb +4 -0
  209. data/try/features/transient_fields/transient_fields_integration_try.rb +4 -0
  210. data/try/integration/connection/fiber_context_preservation_try.rb +4 -0
  211. data/try/integration/connection/handler_constraints_try.rb +4 -0
  212. data/try/integration/connection/isolated_dbclient_try.rb +4 -0
  213. data/try/integration/connection/middleware_reconnect_try.rb +2 -0
  214. data/try/integration/connection/operation_mode_guards_try.rb +4 -0
  215. data/try/integration/connection/pipeline_fallback_integration_try.rb +3 -0
  216. data/try/integration/connection/pools_try.rb +4 -0
  217. data/try/integration/connection/responsibility_chain_tracking_try.rb +4 -0
  218. data/try/integration/connection/transaction_fallback_integration_try.rb +4 -0
  219. data/try/integration/connection/transaction_mode_permissive_try.rb +4 -0
  220. data/try/integration/connection/transaction_mode_strict_try.rb +4 -0
  221. data/try/integration/connection/transaction_mode_warn_try.rb +4 -0
  222. data/try/integration/connection/transaction_modes_try.rb +4 -0
  223. data/try/integration/conventional_inheritance_try.rb +4 -0
  224. data/try/integration/create_method_try.rb +4 -0
  225. data/try/integration/cross_component_try.rb +4 -0
  226. data/try/integration/data_types/datatype_pipelines_try.rb +9 -3
  227. data/try/integration/data_types/datatype_transactions_try.rb +17 -7
  228. data/try/integration/database_consistency_try.rb +4 -0
  229. data/try/integration/familia_extended_try.rb +4 -0
  230. data/try/integration/familia_members_methods_try.rb +4 -0
  231. data/try/integration/models/customer_safe_dump_try.rb +4 -0
  232. data/try/integration/models/customer_try.rb +7 -3
  233. data/try/integration/models/datatype_base_try.rb +4 -0
  234. data/try/integration/models/familia_object_try.rb +4 -0
  235. data/try/integration/persistence_operations_try.rb +4 -0
  236. data/try/integration/relationships_persistence_round_trip_try.rb +17 -14
  237. data/try/integration/save_methods_consistency_try.rb +241 -0
  238. data/try/integration/scenarios_try.rb +4 -0
  239. data/try/integration/secure_identifier_try.rb +4 -0
  240. data/try/integration/transaction_safety_core_try.rb +176 -0
  241. data/try/integration/transaction_safety_workflow_try.rb +291 -0
  242. data/try/integration/verifiable_identifier_try.rb +4 -0
  243. data/try/investigation/pipeline_routing/README.md +228 -0
  244. data/try/performance/benchmarks_try.rb +4 -0
  245. data/try/performance/transaction_safety_benchmark_try.rb +238 -0
  246. data/try/support/benchmarks/deserialization_benchmark.rb +3 -1
  247. data/try/support/benchmarks/deserialization_correctness_test.rb +3 -1
  248. data/try/support/debugging/cache_behavior_tracer.rb +4 -0
  249. data/try/support/debugging/debug_aad_process.rb +3 -0
  250. data/try/support/debugging/debug_concealed_internal.rb +3 -0
  251. data/try/support/debugging/debug_concealed_reveal.rb +3 -0
  252. data/try/support/debugging/debug_context_aad.rb +3 -0
  253. data/try/support/debugging/debug_context_simple.rb +3 -0
  254. data/try/support/debugging/debug_cross_context.rb +3 -0
  255. data/try/support/debugging/debug_database_load.rb +3 -0
  256. data/try/support/debugging/debug_encrypted_json_check.rb +3 -0
  257. data/try/support/debugging/debug_encrypted_json_step_by_step.rb +3 -0
  258. data/try/support/debugging/debug_exists_lifecycle.rb +3 -0
  259. data/try/support/debugging/debug_field_decrypt.rb +3 -0
  260. data/try/support/debugging/debug_fresh_cross_context.rb +3 -0
  261. data/try/support/debugging/debug_load_path.rb +3 -0
  262. data/try/support/debugging/debug_method_definition.rb +3 -0
  263. data/try/support/debugging/debug_method_resolution.rb +3 -0
  264. data/try/support/debugging/debug_minimal.rb +3 -0
  265. data/try/support/debugging/debug_provider.rb +3 -0
  266. data/try/support/debugging/debug_secure_behavior.rb +3 -0
  267. data/try/support/debugging/debug_string_class.rb +3 -0
  268. data/try/support/debugging/debug_test.rb +3 -0
  269. data/try/support/debugging/debug_test_design.rb +3 -0
  270. data/try/support/debugging/encryption_method_tracer.rb +4 -0
  271. data/try/support/debugging/provider_diagnostics.rb +4 -0
  272. data/try/support/helpers/test_cleanup.rb +4 -0
  273. data/try/support/helpers/test_helpers.rb +5 -0
  274. data/try/support/memory/memory_basic_test.rb +4 -0
  275. data/try/support/memory/memory_detailed_test.rb +4 -0
  276. data/try/support/memory/memory_search_for_string.rb +4 -0
  277. data/try/support/memory/test_actual_redactedstring_protection.rb +4 -0
  278. data/try/support/prototypes/atomic_saves_v1_context_proxy.rb +4 -0
  279. data/try/support/prototypes/atomic_saves_v2_connection_switching.rb +4 -0
  280. data/try/support/prototypes/atomic_saves_v3_connection_pool.rb +4 -0
  281. data/try/support/prototypes/atomic_saves_v4.rb +4 -0
  282. data/try/support/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +4 -0
  283. data/try/support/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  284. data/try/support/prototypes/pooling/configurable_stress_test.rb +4 -0
  285. data/try/support/prototypes/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +4 -0
  286. data/try/support/prototypes/pooling/lib/connection_pool_metrics.rb +4 -0
  287. data/try/support/prototypes/pooling/lib/connection_pool_stress_test.rb +4 -0
  288. data/try/support/prototypes/pooling/lib/connection_pool_threading_models.rb +4 -0
  289. data/try/support/prototypes/pooling/lib/visualize_stress_results.rb +4 -2
  290. data/try/support/prototypes/pooling/pool_siege.rb +4 -2
  291. data/try/support/prototypes/pooling/run_stress_tests.rb +4 -2
  292. data/try/thread_safety/README.md +496 -0
  293. data/try/thread_safety/class_connection_chain_race_try.rb +265 -0
  294. data/try/thread_safety/connection_chain_race_try.rb +148 -0
  295. data/try/thread_safety/encryption_manager_cache_race_try.rb +166 -0
  296. data/try/thread_safety/feature_registry_race_try.rb +226 -0
  297. data/try/thread_safety/fiber_pipeline_isolation_try.rb +235 -0
  298. data/try/thread_safety/fiber_transaction_isolation_try.rb +208 -0
  299. data/try/thread_safety/field_registration_race_try.rb +222 -0
  300. data/try/thread_safety/logger_initialization_race_try.rb +170 -0
  301. data/try/thread_safety/middleware_registration_race_try.rb +154 -0
  302. data/try/thread_safety/module_config_race_try.rb +175 -0
  303. data/try/thread_safety/secure_identifier_cache_race_try.rb +226 -0
  304. data/try/unit/core/autoloader_try.rb +4 -0
  305. data/try/unit/core/base_enhancements_try.rb +4 -0
  306. data/try/unit/core/connection_try.rb +4 -0
  307. data/try/unit/core/errors_try.rb +4 -0
  308. data/try/unit/core/extensions_try.rb +4 -0
  309. data/try/unit/core/familia_logger_try.rb +2 -0
  310. data/try/unit/core/familia_try.rb +4 -0
  311. data/try/unit/core/middleware_sampling_try.rb +335 -0
  312. data/try/unit/core/middleware_test_helpers_bug_try.rb +58 -0
  313. data/try/unit/core/middleware_thread_safety_try.rb +245 -0
  314. data/try/unit/core/middleware_try.rb +4 -0
  315. data/try/unit/core/settings_try.rb +4 -0
  316. data/try/unit/core/time_utils_try.rb +4 -0
  317. data/try/unit/core/tools_try.rb +4 -0
  318. data/try/unit/core/utils_try.rb +37 -0
  319. data/try/unit/data_types/boolean_try.rb +39 -22
  320. data/try/unit/data_types/counter_try.rb +4 -0
  321. data/try/unit/data_types/datatype_base_try.rb +4 -0
  322. data/try/unit/data_types/hash_try.rb +6 -2
  323. data/try/unit/data_types/list_try.rb +4 -0
  324. data/try/unit/data_types/lock_try.rb +4 -0
  325. data/try/unit/data_types/serialization_try.rb +386 -0
  326. data/try/unit/data_types/sorted_set_try.rb +4 -0
  327. data/try/unit/data_types/sorted_set_zadd_options_try.rb +4 -0
  328. data/try/unit/data_types/string_try.rb +4 -0
  329. data/try/unit/data_types/unsortedset_try.rb +4 -0
  330. data/try/unit/familia_resolve_class_try.rb +116 -0
  331. data/try/unit/horreum/auto_indexing_on_save_try.rb +5 -1
  332. data/try/unit/horreum/automatic_index_validation_try.rb +2 -0
  333. data/try/unit/horreum/base_try.rb +4 -0
  334. data/try/unit/horreum/class_methods_try.rb +4 -0
  335. data/try/unit/horreum/commands_try.rb +4 -0
  336. data/try/unit/horreum/defensive_initialization_try.rb +4 -0
  337. data/try/unit/horreum/destroy_related_fields_cleanup_try.rb +6 -1
  338. data/try/unit/horreum/enhanced_conflict_handling_try.rb +4 -0
  339. data/try/unit/horreum/field_categories_try.rb +4 -0
  340. data/try/unit/horreum/field_definition_try.rb +4 -0
  341. data/try/unit/horreum/initialization_try.rb +4 -0
  342. data/try/unit/horreum/json_type_preservation_try.rb +2 -0
  343. data/try/unit/horreum/optimized_loading_try.rb +156 -0
  344. data/try/unit/horreum/relations_try.rb +4 -0
  345. data/try/unit/horreum/serialization_persistent_fields_try.rb +4 -0
  346. data/try/unit/horreum/serialization_try.rb +4 -0
  347. data/try/unit/horreum/settings_try.rb +4 -0
  348. data/try/unit/horreum/unique_index_edge_cases_try.rb +4 -0
  349. data/try/unit/horreum/unique_index_guard_validation_try.rb +2 -0
  350. data/try/unit/middleware/database_command_counter_methods_try.rb +139 -0
  351. data/try/unit/middleware/database_logger_methods_try.rb +251 -0
  352. data/try/unit/refinements/dear_json_array_methods_try.rb +4 -0
  353. data/try/unit/refinements/dear_json_hash_methods_try.rb +4 -0
  354. data/try/unit/refinements/time_literals_numeric_methods_try.rb +4 -0
  355. data/try/unit/refinements/time_literals_string_methods_try.rb +4 -0
  356. data/try/unit/thread_safety_monitor_try.rb +149 -0
  357. metadata +69 -17
  358. data/.github/workflows/code-quality.yml +0 -138
  359. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +0 -91
  360. data/changelog.d/20251011_203905_delano_next.rst +0 -30
  361. data/changelog.d/20251011_212633_delano_next.rst +0 -13
  362. data/changelog.d/20251011_221253_delano_next.rst +0 -26
  363. data/docs/archive/FAMILIA_RELATIONSHIPS.md +0 -210
  364. data/docs/archive/FAMILIA_TECHNICAL.md +0 -823
  365. data/docs/archive/FAMILIA_UPDATE.md +0 -226
  366. data/docs/archive/README.md +0 -64
  367. data/docs/archive/api-reference.md +0 -333
  368. data/docs/guides/core-field-system.md +0 -806
  369. data/docs/guides/implementation.md +0 -276
  370. data/docs/guides/security-model.md +0 -183
@@ -0,0 +1,242 @@
1
+ # try/features/relationships/participation_bidirectional_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ require_relative '../../../lib/familia'
6
+
7
+ # Test demonstrating true bidirectional participation functionality
8
+ # This shows the improvement from asymmetric to symmetric relationship access
9
+
10
+ # Setup test classes
11
+ class ::ProjectTeam < Familia::Horreum
12
+ feature :relationships
13
+
14
+ identifier_field :team_id
15
+ field :team_id
16
+ field :name
17
+ field :department
18
+
19
+ # Explicitly declare collections for participates_in
20
+ sorted_set :members
21
+ sorted_set :admins
22
+
23
+ def init
24
+ @team_id ||= "team_#{SecureRandom.hex(4)}"
25
+ end
26
+ end
27
+
28
+ class ::ProjectOrganization < Familia::Horreum
29
+ feature :relationships
30
+
31
+ identifier_field :org_id
32
+ field :org_id
33
+ field :name
34
+ field :type
35
+
36
+ # Explicitly declare collections for participates_in
37
+ sorted_set :employees
38
+ sorted_set :contractors
39
+
40
+ def init
41
+ @org_id ||= "org_#{SecureRandom.hex(4)}"
42
+ end
43
+ end
44
+
45
+ class ::ProjectUser < Familia::Horreum
46
+ feature :relationships
47
+
48
+ identifier_field :user_id
49
+ field :user_id
50
+ field :email
51
+ field :name
52
+ field :role
53
+
54
+ # Define bidirectional participation relationships
55
+ # These will auto-generate reverse collection methods with _instances suffix
56
+ participates_in ProjectTeam, :members # Generates: user.project_team_instances
57
+ participates_in ProjectTeam, :admins # Also adds to user.project_team_instances (union)
58
+ participates_in ProjectOrganization, :employees # Generates: user.project_organization_instances
59
+
60
+ # Custom reverse method name (user chooses the base name)
61
+ participates_in ProjectOrganization, :contractors, as: :contracting_orgs
62
+
63
+ def init
64
+ @user_id ||= "user_#{SecureRandom.hex(4)}"
65
+ end
66
+ end
67
+
68
+ # Create test data
69
+ @user1 = ProjectUser.new(email: 'alice@example.com', name: 'Alice', role: 'developer')
70
+ @user2 = ProjectUser.new(email: 'bob@example.com', name: 'Bob', role: 'manager')
71
+
72
+ @team1 = ProjectTeam.new(name: 'Frontend Team', department: 'Engineering')
73
+ @team2 = ProjectTeam.new(name: 'Backend Team', department: 'Engineering')
74
+ @team3 = ProjectTeam.new(name: 'Design Team', department: 'Product')
75
+
76
+ @org1 = ProjectOrganization.new(name: 'TechCorp Inc', type: 'employer')
77
+ @org2 = ProjectOrganization.new(name: 'FreelanceCorp', type: 'contractor')
78
+
79
+ # Save all objects
80
+ [@user1, @user2, @team1, @team2, @team3, @org1, @org2].each(&:save)
81
+
82
+ # Set up relationships using generated methods (automatic participation tracking)
83
+ @team1.add_members_instance(@user1)
84
+ @team2.add_members_instance(@user1)
85
+ @team1.add_admins_instance(@user1)
86
+ @org1.add_employees_instance(@user1)
87
+ @org2.add_contractors_instance(@user1)
88
+
89
+ # Set up user2 in fewer relationships
90
+ @team3.add_members_instance(@user2)
91
+ @org1.add_employees_instance(@user2)
92
+
93
+ ## Test the OLD way - difficult and verbose
94
+ # Before: Getting teams for a user required manual parsing
95
+ old_way_keys = @user1.participations.members.select { |k| k.start_with?("project_team:") && k.end_with?(":members") }
96
+ old_way_ids = old_way_keys.map { |k| k.split(':')[1] }.uniq
97
+ old_way_teams = ProjectTeam.load_multi(old_way_ids).compact
98
+ old_way_teams.map(&:name).sort
99
+ #=> ["Backend Team", "Frontend Team"]
100
+
101
+ ## Debug - Check if methods are defined
102
+ @user1.respond_to?(:project_team_instances)
103
+ #=> true
104
+
105
+ ## Debug - Check participations data
106
+ @user1.participations.members
107
+ #=> ["project_team:#{@team1.identifier}:members", "project_team:#{@team2.identifier}:members", "project_team:#{@team1.identifier}:admins", "project_organization:#{@org1.identifier}:employees", "project_organization:#{@org2.identifier}:contractors"]
108
+
109
+ ## Debug - Check if participating_ids_for_target works
110
+ ids = @user1.participating_ids_for_target(ProjectTeam)
111
+ ids
112
+ #=> [@team1.identifier, @team2.identifier]
113
+
114
+ ## Debug - Test individual team loading
115
+ ProjectTeam.load(@team1.identifier).name
116
+ #=> "Frontend Team"
117
+
118
+ ## Debug - Test load_multi with actual IDs
119
+ test_ids = [@team1.identifier, @team2.identifier]
120
+ ProjectTeam.load_multi(test_ids).compact.map(&:name).sort
121
+ #=> ["Backend Team", "Frontend Team"]
122
+
123
+ ## Debug - Check what project_teams method returns
124
+ result = @user1.project_team_instances
125
+ puts "project_teams method returns: #{result.inspect}"
126
+ result.map(&:name).sort
127
+ #=> ["Backend Team", "Frontend Team"]
128
+
129
+ ## Test NEW way - clean and intuitive reverse collection method
130
+ user_teams = @user1.project_team_instances # Auto-generated pluralized from ProjectTeam class name
131
+ user_teams.map(&:name).sort
132
+ #=> ["Backend Team", "Frontend Team"]
133
+
134
+ ## Test that both users and admins collections are included (union behavior)
135
+ all_team_participations = @user1.project_team_instances # Should include both members and admins
136
+ all_team_participations.map(&:name).sort
137
+ #=> ["Backend Team", "Frontend Team"]
138
+
139
+ ## Test IDs-only method (efficient, no object loading)
140
+ user_team_ids = @user1.project_team_ids
141
+ user_team_ids.sort
142
+ #=> [@team1.identifier, @team2.identifier].sort
143
+
144
+ ## Test boolean check method
145
+ @user1.project_team?
146
+ #=> true
147
+
148
+ ## Test count method returns correct number
149
+ @user1.project_team_count
150
+ #=> 2
151
+
152
+ ## Test organizations (different target class)
153
+ user_orgs = @user1.project_organization_instances
154
+ user_orgs.map(&:name).sort
155
+ #=> ["FreelanceCorp", "TechCorp Inc"]
156
+
157
+ ## Test custom reverse method name
158
+ contracting_orgs_instances = @user1.contracting_orgs_instances
159
+ contracting_orgs_instances.map(&:name)
160
+ #=> ["FreelanceCorp"]
161
+
162
+ ## Test user with fewer relationships
163
+ @user2.project_team_instances.map(&:name)
164
+ #=> ["Design Team"]
165
+
166
+ ## Test user2 count
167
+ @user2.project_team_count
168
+ #=> 1
169
+
170
+ ## Test user2 organizations
171
+ @user2.project_organization_instances.map(&:name)
172
+ #=> ["TechCorp Inc"]
173
+
174
+ ## Test create empty user with no memberships
175
+ @user3 = ProjectUser.new(email: 'charlie@example.com', name: 'Charlie')
176
+ @user3.save
177
+ @user3.project_team_instances
178
+ #=> []
179
+
180
+ ## Test empty user with IDs, check and count
181
+ @user3.project_team_ids
182
+ #=> []
183
+
184
+ ## Test empty user boolean check
185
+ @user3.project_team?
186
+ #=> false
187
+
188
+ ## Test empty user count
189
+ @user3.project_team_count
190
+ #=> 0
191
+
192
+ ## Test that forward direction still works (backwards compatibility)
193
+ @team1.members.to_a.sort
194
+ #=> [@user1.identifier]
195
+
196
+ ## Test admin collection forward direction
197
+ @team1.admins.to_a
198
+ #=> [@user1.identifier]
199
+
200
+ ## Test bidirectional consistency - forward direction
201
+ team1_member_ids = @team1.members.to_a
202
+ team1_members = ProjectUser.load_multi(team1_member_ids).compact
203
+ team1_members.map(&:name)
204
+ #=> ["Alice"]
205
+
206
+ ## Test bidirectional consistency - reverse direction
207
+ user1_teams = @user1.project_team_instances
208
+ user1_team_ids = user1_teams.map(&:identifier)
209
+ user1_team_ids.include?(@team1.identifier)
210
+ #=> true
211
+
212
+ ## Test multiple users in same team - add user2
213
+ @team1.add_members_instance(@user2)
214
+ @team1.members.member?(@user2.identifier)
215
+ #=> true
216
+
217
+ ## Test team1 now has two members
218
+ team1_all_members = @team1.members.to_a.sort
219
+ team1_all_members == [@user1.identifier, @user2.identifier].sort
220
+ #=> true
221
+
222
+ ## Test both users show up in team1 - user1
223
+ @user1.project_team_instances.map(&:name).include?("Frontend Team")
224
+ #=> true
225
+
226
+ ## Test both users show up in team1 - user2
227
+ @user2.project_team_instances.map(&:name).include?("Frontend Team")
228
+ #=> true
229
+
230
+ ## Test cleanup - remove relationships using generated method
231
+ @team1.remove_members_instance(@user1)
232
+
233
+ ## Test that removal from members is reflected (no caching)
234
+ # User1 was removed from :members but is still in :admins
235
+ # So they should STILL appear in project_team_instances (union behavior)
236
+ updated_teams = @user1.project_team_instances.map(&:name).sort
237
+ updated_teams.include?("Frontend Team")
238
+ #=> true
239
+
240
+ ## Test still admin of team1 after member removal (confirms union behavior)
241
+ @user1.project_team_instances.map(&:name).include?("Frontend Team")
242
+ #=> true
@@ -1,3 +1,7 @@
1
+ # try/features/relationships/participation_commands_verification_spec.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
1
5
  # Generated rspec code for /Users/d/Projects/opensource/d/familia/try/features/relationships/participation_commands_verification_try.rb
2
6
  # Updated: 2025-09-26 21:27:49 -0700
3
7
 
@@ -1,5 +1,7 @@
1
1
  # try/features/relationships/participation_commands_verification_try.rb
2
2
  #
3
+ # frozen_string_literal: true
4
+
3
5
  # Based on participation reverse index functionality tests.
4
6
  # Validates participation functionality and command isolation
5
7
  #
@@ -1,12 +1,14 @@
1
1
  # try/features/relationships/participation_performance_improvements_try.rb
2
2
  #
3
+ # frozen_string_literal: true
4
+
3
5
  # Tests for performance improvements in participation functionality
4
6
  # Verifies reverse index functionality and robust type comparison
5
7
 
6
8
  require_relative '../../support/helpers/test_helpers'
7
9
 
8
10
  # Test classes for performance improvements
9
- class PerfTestCustomer < Familia::Horreum
11
+ class ::PerfTestCustomer < Familia::Horreum
10
12
  feature :relationships
11
13
 
12
14
  identifier_field :customer_id
@@ -16,7 +18,7 @@ class PerfTestCustomer < Familia::Horreum
16
18
  sorted_set :domains
17
19
  end
18
20
 
19
- class PerfTestDomain < Familia::Horreum
21
+ class ::PerfTestDomain < Familia::Horreum
20
22
  feature :relationships
21
23
 
22
24
  identifier_field :domain_id
@@ -51,7 +53,7 @@ end
51
53
  #=> true
52
54
 
53
55
  ## Test add domain creates reverse index tracking 1 of 2
54
- @customer.add_domain(@domain)
56
+ @customer.add_domains_instance(@domain)
55
57
  @reverse_index_key = "#{@domain.dbkey}:participations"
56
58
  @tracked_collections = Familia.dbclient.smembers(@reverse_index_key) # TODO: Do not use keys directly
57
59
  @tracked_collections.length
@@ -64,13 +66,13 @@ end
64
66
  ##=> true
65
67
 
66
68
  ## Test remove domain cleans up reverse index tracking
67
- @customer.remove_domain(@domain)
69
+ @customer.remove_domains_instance(@domain)
68
70
  @tracked_collections_after_remove = Familia.dbclient.smembers(@reverse_index_key) # TODO: Do not use keys directly
69
71
  @tracked_collections_after_remove.include?(@collection_key)
70
72
  ##=> false
71
73
 
72
74
  ## Test robust type comparison in score calculation works with Class
73
- @customer.add_domain(@domain)
75
+ @customer.add_domains_instance(@domain)
74
76
  @score_with_class = @domain.calculate_participation_score(PerfTestCustomer, :domains)
75
77
  @score_with_class.is_a?(Numeric)
76
78
  #=> true
@@ -93,16 +95,16 @@ end
93
95
  ## Test membership contains expected target class
94
96
  @memberships = @domain.current_participations
95
97
  @membership = @memberships.first
96
- @membership[:target_class] == 'PerfTestCustomer'
98
+ @membership.target_class == 'PerfTestCustomer'
97
99
  #=> true
98
100
 
99
101
  ## Test membership contains collection name
100
102
  @memberships = @domain.current_participations
101
- @membership[:collection_name] == :domains
103
+ @membership.collection_name == :domains
102
104
  #=> true
103
105
 
104
106
  ## Test membership contains type information
105
- @membership[:type] == :sorted_set
107
+ @membership.type == :sorted_set
106
108
  #=> true
107
109
 
108
110
  ## Test remove from all participation collections works efficiently
@@ -112,7 +114,7 @@ end
112
114
  ##=> true
113
115
 
114
116
  ## Test domain is removed from customer collection
115
- @customer.remove_domain(@domain)
117
+ @customer.remove_domains_instance(@domain)
116
118
  @customer.domains.include?(@domain)
117
119
  #=> false
118
120
 
@@ -1,5 +1,7 @@
1
1
  # try/features/relationships/participation_reverse_index_try.rb
2
2
  #
3
+ # frozen_string_literal: true
4
+
3
5
  # Tests for participation reverse index functionality
4
6
  # Verifies performance improvements and correct behavior
5
7
 
@@ -52,7 +54,7 @@ end
52
54
  @domain2.save
53
55
 
54
56
  ## Test reverse index tracking is created when adding to sorted set
55
- @customer.add_domain(@domain1)
57
+ @customer.add_domains_instance(@domain1)
56
58
  @ri_members1 = @domain1.participations.members
57
59
  @ri_members1.is_a?(Array)
58
60
  #=> true
@@ -63,7 +65,7 @@ end
63
65
  #=> true
64
66
 
65
67
  ## Test adding to set collection also creates tracking
66
- @customer.add_preferred_domain(@domain1)
68
+ @customer.add_preferred_domains_instance(@domain1)
67
69
  @ri_members1_updated = @domain1.participations.members
68
70
  @ri_members1_updated.length > 1
69
71
  #=> true
@@ -81,7 +83,7 @@ end
81
83
  #=> true
82
84
 
83
85
  ## Test multiple domains can be tracked
84
- @customer.add_domain(@domain2)
86
+ @customer.add_domains_instance(@domain2)
85
87
  @ri_members2_updated = @domain2.participations.members
86
88
  @ri_members2_updated.length >= 1
87
89
  #=> true
@@ -94,8 +96,8 @@ end
94
96
 
95
97
  ## Test remove_from_all_participations uses reverse index
96
98
  # NOTE: This method was removed - cleanup happens via individual remove operations
97
- @customer.remove_domain(@domain1)
98
- @customer.remove_preferred_domain(@domain1)
99
+ @customer.remove_domains_instance(@domain1)
100
+ @customer.remove_preferred_domains_instance(@domain1)
99
101
  @domain1_collections_after = @domain1.participations.members
100
102
  @domain1_collections_after.empty?
101
103
  #=> true
@@ -106,7 +108,7 @@ end
106
108
  #=> false
107
109
 
108
110
  ## Test domain was removed from set
109
- @customer.remove_preferred_domain(@domain1)
111
+ @customer.remove_preferred_domains_instance(@domain1)
110
112
  @customer.preferred_domains.include?(@domain1)
111
113
  #=> false
112
114
 
@@ -120,12 +122,12 @@ end
120
122
  #=> true
121
123
 
122
124
  ## Test membership includes target information
123
- @customer_membership = @domain2_memberships.find { |m| m[:target_id] == @customer.identifier }
124
- @customer_membership.is_a?(Hash) || @customer_membership.nil?
125
+ @customer_membership = @domain2_memberships.find { |m| m.target_id == @customer.identifier }
126
+ @customer_membership.is_a?(Familia::Features::Relationships::ParticipationMembership) || @customer_membership.nil?
125
127
  #=> true
126
128
 
127
129
  ## Test membership includes collection name
128
- @customer_membership && @customer_membership[:collection_name] == :domains || true
130
+ @customer_membership && @customer_membership.collection_name == :domains || true
129
131
  #=> true
130
132
 
131
133
  ## Test score type comparison works with different types
@@ -151,7 +153,7 @@ puts "Before add_preferred_domain - domain2 participations: #{@domain2.participa
151
153
  puts "Before add_preferred_domain - domain2 in domains?: #{@customer.domains.member?(@domain2.identifier)}"
152
154
 
153
155
  # Add domain to multiple collections
154
- @customer.add_preferred_domain(@domain2)
156
+ @customer.add_preferred_domains_instance(@domain2)
155
157
 
156
158
  # Debug: Check what participations we actually have after
157
159
  puts "After add_preferred_domain - domain2 participations: #{@domain2.participations.members.inspect}"
@@ -172,14 +174,14 @@ puts "Domain2 participations length: #{@domain2_final_memberships.length}"
172
174
  ## Test cleanup removes all participations
173
175
  # Manual cleanup since remove_from_all_participations was removed
174
176
  # Remove from all collections domain2 participates in
175
- @customer.remove_domain(@domain2)
176
- @customer.remove_preferred_domain(@domain2) if @customer.preferred_domains.member?(@domain2.identifier)
177
+ @customer.remove_domains_instance(@domain2)
178
+ @customer.remove_preferred_domains_instance(@domain2) if @customer.preferred_domains.member?(@domain2.identifier)
177
179
  @ri_members2_final = @domain2.participations.members
178
180
  @ri_members2_final.empty?
179
181
  ##=> true
180
182
 
181
183
  ## Test domain2 removed from all collections
182
- @customer.remove_domain(@domain2)
184
+ @customer.remove_domains_instance(@domain2)
183
185
  @customer.domains.include?(@domain2)
184
186
  #=> false
185
187
 
@@ -0,0 +1,209 @@
1
+ # try/features/relationships/participation_target_class_resolution_try.rb
2
+ #
3
+ # frozen_string_literal: true
4
+
5
+ # Regression test for Symbol/String target class resolution in participates_in
6
+ #
7
+ # This test verifies the fix for the NoMethodError that occurred when
8
+ # participates_in was called with a Symbol or String target class instead
9
+ # of a Class object. The error was:
10
+ # "private method 'member_by_config_name' called for module Familia"
11
+ #
12
+ # See commit: Fix NoMethodError when calling private member_by_config_name
13
+
14
+ require_relative '../../support/helpers/test_helpers'
15
+
16
+ # Test classes for target class resolution
17
+ # Define target class FIRST so it's registered in Familia.members
18
+ class SymbolResolutionCustomer < Familia::Horreum
19
+ feature :relationships
20
+
21
+ identifier_field :customer_id
22
+ field :customer_id
23
+ field :name
24
+
25
+ sorted_set :domains
26
+ set :tags
27
+ end
28
+
29
+ # Participant classes using different target class formats
30
+ class SymbolResolutionDomain < Familia::Horreum
31
+ feature :relationships
32
+
33
+ identifier_field :domain_id
34
+ field :domain_id
35
+ field :name
36
+ field :created_at
37
+
38
+ # TEST CASE 1: Symbol target class (most common case)
39
+ # This was causing: NoMethodError: private method 'member_by_config_name'
40
+ participates_in :SymbolResolutionCustomer, :domains, score: :created_at
41
+ end
42
+
43
+ class StringResolutionTag < Familia::Horreum
44
+ feature :relationships
45
+
46
+ identifier_field :tag_id
47
+ field :tag_id
48
+ field :name
49
+ field :created_at
50
+
51
+ # TEST CASE 2: String target class
52
+ # This was also causing the same NoMethodError
53
+ # Note: Customer's tags is a set (not sorted_set), so specify type: :set
54
+ participates_in 'SymbolResolutionCustomer', :tags, type: :set
55
+ end
56
+
57
+ class ClassResolutionItem < Familia::Horreum
58
+ feature :relationships
59
+
60
+ identifier_field :item_id
61
+ field :item_id
62
+ field :name
63
+ field :created_at
64
+
65
+ # TEST CASE 3: Class object (this always worked)
66
+ # Including for completeness
67
+ sorted_set :items
68
+ end
69
+
70
+ # Setup test data
71
+ @customer = SymbolResolutionCustomer.new(
72
+ customer_id: 'symbol_res_cust_1',
73
+ name: 'Symbol Resolution Customer'
74
+ )
75
+ @domain = SymbolResolutionDomain.new(
76
+ domain_id: 'symbol_res_dom_1',
77
+ name: 'example.com',
78
+ created_at: Time.now.to_f
79
+ )
80
+ @tag = StringResolutionTag.new(
81
+ tag_id: 'string_res_tag_1',
82
+ name: 'important',
83
+ created_at: Time.now.to_f
84
+ )
85
+
86
+ # Ensure clean state
87
+ [@customer, @domain, @tag].each do |obj|
88
+ obj.destroy if obj&.respond_to?(:destroy) && obj&.respond_to?(:exists?) && obj.exists?
89
+ end
90
+
91
+ @customer.save
92
+ @domain.save
93
+ @tag.save
94
+
95
+ ## Test Symbol target class resolution works
96
+ # This is the primary regression test - it should not raise NoMethodError
97
+ @customer.add_domains_instance(@domain)
98
+ @customer.domains.member?(@domain.identifier)
99
+ #=> true
100
+
101
+ ## Test domain bidirectional methods were created correctly
102
+ @domain.respond_to?(:in_symbol_resolution_customer_domains?)
103
+ #=> true
104
+
105
+ ## Test domain can check membership using generated method
106
+ @domain.in_symbol_resolution_customer_domains?(@customer)
107
+ #=> true
108
+
109
+ ## Test domain can add itself using generated method
110
+ @customer.domains.remove(@domain)
111
+ @domain.add_to_symbol_resolution_customer_domains(@customer)
112
+ @customer.domains.member?(@domain.identifier)
113
+ #=> true
114
+
115
+ ## Test domain score calculation works with Symbol target class
116
+ @score = @domain.calculate_participation_score(:SymbolResolutionCustomer, :domains)
117
+ @score.is_a?(Numeric)
118
+ #=> true
119
+
120
+ ## Test domain score matches created_at field
121
+ (@score - @domain.created_at).abs < 0.001
122
+ #=> true
123
+
124
+ ## Test String target class resolution works
125
+ # This is the secondary regression test
126
+ @customer.add_tags_instance(@tag)
127
+ @customer.tags.member?(@tag.identifier)
128
+ #=> true
129
+
130
+ ## Test tag bidirectional methods were created correctly
131
+ @tag.respond_to?(:in_symbol_resolution_customer_tags?)
132
+ #=> true
133
+
134
+ ## Test tag can check membership using generated method
135
+ @tag.in_symbol_resolution_customer_tags?(@customer)
136
+ #=> true
137
+
138
+ ## Test tag can add itself using generated method
139
+ @customer.tags.remove(@tag)
140
+ @tag.add_to_symbol_resolution_customer_tags(@customer)
141
+ @customer.tags.member?(@tag.identifier)
142
+ #=> true
143
+
144
+ ## Test tag membership check works with String target class (sets don't have scores)
145
+ @tag.in_symbol_resolution_customer_tags?(@customer)
146
+ #=> true
147
+
148
+ ## Test reverse index tracking works with Symbol target class
149
+ @domain.participations.members.length > 0
150
+ #=> true
151
+
152
+ ## Test reverse index contains the correct collection key
153
+ @domains_key = @customer.domains.dbkey
154
+ @domain.participations.members.include?(@domains_key)
155
+ #=> true
156
+
157
+ ## Test reverse index tracking works with String target class
158
+ @tag.participations.members.length > 0
159
+ #=> true
160
+
161
+ ## Test reverse index contains the correct collection key for tags
162
+ @tags_key = @customer.tags.dbkey
163
+ @tag.participations.members.include?(@tags_key)
164
+ #=> true
165
+
166
+ ## Test current_participations works with Symbol target class
167
+ @domain_participations = @domain.current_participations
168
+ @domain_participations.is_a?(Array)
169
+ #=> true
170
+
171
+ ## Test participation data includes correct target class
172
+ @domain_participation = @domain_participations.first
173
+ @domain_participation.target_class == 'SymbolResolutionCustomer'
174
+ #=> true
175
+
176
+ ## Test current_participations works with String target class
177
+ @tag_participations = @tag.current_participations
178
+ @tag_participations.is_a?(Array)
179
+ #=> true
180
+
181
+ ## Test participation data includes correct collection name
182
+ @tag_participation = @tag_participations.first
183
+ @tag_participation.collection_name == :tags
184
+ #=> true
185
+
186
+ ## Test removal works correctly with Symbol target class
187
+ @customer.remove_domains_instance(@domain)
188
+ @customer.domains.member?(@domain.identifier)
189
+ #=> false
190
+
191
+ ## Test reverse index cleanup with Symbol target class
192
+ @domain.participations.members.empty?
193
+ #=> true
194
+
195
+ ## Test removal works correctly with String target class
196
+ @customer.remove_tags_instance(@tag)
197
+ @customer.tags.member?(@tag.identifier)
198
+ #=> false
199
+
200
+ ## Test reverse index cleanup with String target class
201
+ @tag.participations.members.empty?
202
+ #=> true
203
+
204
+ ## Cleanup
205
+ [@customer, @domain, @tag].each do |obj|
206
+ obj.destroy if obj&.respond_to?(:destroy) && obj&.respond_to?(:exists?) && obj.exists?
207
+ end
208
+ true
209
+ #=> true