familia 2.0.0.pre17 → 2.0.0.pre19

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rst +118 -6
  3. data/CLAUDE.md +43 -11
  4. data/Gemfile +2 -2
  5. data/Gemfile.lock +9 -47
  6. data/README.md +52 -0
  7. data/bin/irb +1 -1
  8. data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +91 -0
  9. data/changelog.d/20251011_203905_delano_next.rst +30 -0
  10. data/changelog.d/20251011_212633_delano_next.rst +13 -0
  11. data/changelog.d/20251011_221253_delano_next.rst +26 -0
  12. data/docs/guides/core-field-system.md +48 -26
  13. data/docs/guides/feature-expiration.md +18 -18
  14. data/docs/migrating/v2.0.0-pre18.md +58 -0
  15. data/docs/migrating/v2.0.0-pre19.md +197 -0
  16. data/docs/qodo-merge-compliance.md +96 -0
  17. data/examples/datatype_standalone.rb +281 -0
  18. data/lib/familia/base.rb +0 -2
  19. data/lib/familia/connection/behavior.rb +252 -0
  20. data/lib/familia/connection/handlers.rb +95 -0
  21. data/lib/familia/connection/middleware.rb +58 -4
  22. data/lib/familia/connection/operation_core.rb +1 -1
  23. data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +2 -2
  24. data/lib/familia/connection/transaction_core.rb +7 -9
  25. data/lib/familia/connection.rb +2 -1
  26. data/lib/familia/data_type/connection.rb +151 -7
  27. data/lib/familia/data_type/{commands.rb → database_commands.rb} +9 -6
  28. data/lib/familia/data_type/serialization.rb +9 -5
  29. data/lib/familia/data_type/types/hashkey.rb +1 -1
  30. data/lib/familia/data_type.rb +2 -2
  31. data/lib/familia/encryption/encrypted_data.rb +12 -2
  32. data/lib/familia/encryption/manager.rb +11 -4
  33. data/lib/familia/errors.rb +51 -14
  34. data/lib/familia/features/autoloader.rb +3 -1
  35. data/lib/familia/features/encrypted_fields/encrypted_field_type.rb +11 -3
  36. data/lib/familia/features/expiration/extensions.rb +8 -10
  37. data/lib/familia/features/expiration.rb +19 -19
  38. data/lib/familia/features/relationships/indexing/multi_index_generators.rb +45 -44
  39. data/lib/familia/features/relationships/indexing/unique_index_generators.rb +151 -65
  40. data/lib/familia/features/relationships/indexing.rb +37 -42
  41. data/lib/familia/features/relationships/indexing_relationship.rb +14 -4
  42. data/lib/familia/features/safe_dump.rb +2 -3
  43. data/lib/familia/field_type.rb +2 -1
  44. data/lib/familia/horreum/connection.rb +11 -35
  45. data/lib/familia/horreum/database_commands.rb +130 -11
  46. data/lib/familia/horreum/definition.rb +8 -38
  47. data/lib/familia/horreum/management.rb +38 -27
  48. data/lib/familia/horreum/persistence.rb +191 -67
  49. data/lib/familia/horreum/serialization.rb +94 -73
  50. data/lib/familia/horreum/utils.rb +0 -8
  51. data/lib/familia/horreum.rb +41 -18
  52. data/lib/familia/identifier_extractor.rb +60 -0
  53. data/lib/familia/logging.rb +268 -112
  54. data/lib/familia/refinements.rb +0 -1
  55. data/lib/familia/settings.rb +7 -7
  56. data/lib/familia/version.rb +1 -1
  57. data/lib/familia.rb +2 -2
  58. data/lib/middleware/{database_middleware.rb → database_logger.rb} +118 -14
  59. data/pr_agent.toml +31 -0
  60. data/pr_compliance_checklist.yaml +45 -0
  61. data/try/edge_cases/empty_identifiers_try.rb +1 -1
  62. data/try/edge_cases/hash_symbolization_try.rb +31 -31
  63. data/try/edge_cases/json_serialization_try.rb +2 -2
  64. data/try/edge_cases/legacy_data_detection/deserialization_edge_cases_try.rb +170 -0
  65. data/try/edge_cases/race_conditions_try.rb +1 -1
  66. data/try/edge_cases/reserved_keywords_try.rb +1 -1
  67. data/try/edge_cases/string_coercion_try.rb +5 -5
  68. data/try/edge_cases/ttl_side_effects_try.rb +1 -1
  69. data/try/features/encrypted_fields/aad_protection_try.rb +1 -1
  70. data/try/features/encrypted_fields/concealed_string_core_try.rb +1 -1
  71. data/try/features/encrypted_fields/context_isolation_try.rb +1 -1
  72. data/try/features/encrypted_fields/encrypted_fields_core_try.rb +1 -1
  73. data/try/features/encrypted_fields/encrypted_fields_integration_try.rb +1 -1
  74. data/try/features/encrypted_fields/encrypted_fields_no_cache_security_try.rb +1 -1
  75. data/try/features/encrypted_fields/encrypted_fields_security_try.rb +1 -1
  76. data/try/features/encrypted_fields/error_conditions_try.rb +1 -1
  77. data/try/features/encrypted_fields/fresh_key_derivation_try.rb +1 -1
  78. data/try/features/encrypted_fields/fresh_key_try.rb +1 -1
  79. data/try/features/encrypted_fields/key_rotation_try.rb +1 -1
  80. data/try/features/encrypted_fields/memory_security_try.rb +1 -1
  81. data/try/features/encrypted_fields/missing_current_key_version_try.rb +1 -1
  82. data/try/features/encrypted_fields/nonce_uniqueness_try.rb +1 -1
  83. data/try/features/encrypted_fields/secure_by_default_behavior_try.rb +1 -1
  84. data/try/features/encrypted_fields/thread_safety_try.rb +1 -1
  85. data/try/features/encrypted_fields/universal_serialization_safety_try.rb +1 -1
  86. data/try/{encryption → features/encryption}/config_persistence_try.rb +1 -1
  87. data/try/{encryption/encryption_core_try.rb → features/encryption/core_try.rb} +2 -2
  88. data/try/{encryption → features/encryption}/instance_variable_scope_try.rb +1 -1
  89. data/try/{encryption → features/encryption}/module_loading_try.rb +1 -1
  90. data/try/{encryption → features/encryption}/providers/aes_gcm_provider_try.rb +1 -1
  91. data/try/{encryption → features/encryption}/providers/xchacha20_poly1305_provider_try.rb +1 -1
  92. data/try/{encryption → features/encryption}/roundtrip_validation_try.rb +1 -1
  93. data/try/{encryption → features/encryption}/secure_memory_handling_try.rb +2 -2
  94. data/try/features/expiration/expiration_try.rb +2 -2
  95. data/try/features/external_identifier/external_identifier_try.rb +1 -1
  96. data/try/features/feature_dependencies_try.rb +1 -1
  97. data/try/features/feature_improvements_try.rb +1 -1
  98. data/try/features/object_identifier/object_identifier_integration_try.rb +1 -1
  99. data/try/features/object_identifier/object_identifier_try.rb +1 -1
  100. data/try/features/quantization/quantization_try.rb +1 -1
  101. data/try/features/real_feature_integration_try.rb +17 -14
  102. data/try/features/relationships/indexing_commands_verification_try.rb +8 -3
  103. data/try/features/relationships/indexing_try.rb +34 -5
  104. data/try/features/relationships/participation_commands_verification_spec.rb +1 -1
  105. data/try/features/relationships/participation_commands_verification_try.rb +4 -4
  106. data/try/features/relationships/participation_performance_improvements_try.rb +1 -1
  107. data/try/features/relationships/participation_reverse_index_try.rb +1 -1
  108. data/try/features/relationships/relationships_api_changes_try.rb +5 -5
  109. data/try/features/relationships/relationships_edge_cases_try.rb +3 -3
  110. data/try/features/relationships/relationships_performance_minimal_try.rb +1 -1
  111. data/try/features/relationships/relationships_performance_simple_try.rb +1 -1
  112. data/try/features/relationships/relationships_performance_try.rb +1 -1
  113. data/try/features/relationships/relationships_performance_working_try.rb +1 -1
  114. data/try/features/relationships/relationships_try.rb +1 -1
  115. data/try/features/safe_dump/safe_dump_advanced_try.rb +1 -1
  116. data/try/features/safe_dump/safe_dump_try.rb +1 -1
  117. data/try/features/transient_fields/redacted_string_try.rb +1 -1
  118. data/try/features/transient_fields/refresh_reset_try.rb +1 -1
  119. data/try/features/transient_fields/single_use_redacted_string_try.rb +1 -1
  120. data/try/features/transient_fields/transient_fields_core_try.rb +1 -1
  121. data/try/features/transient_fields/transient_fields_integration_try.rb +1 -1
  122. data/try/{connection → integration/connection}/fiber_context_preservation_try.rb +4 -4
  123. data/try/{connection → integration/connection}/handler_constraints_try.rb +1 -1
  124. data/try/{core → integration/connection}/isolated_dbclient_try.rb +1 -1
  125. data/try/integration/connection/middleware_reconnect_try.rb +87 -0
  126. data/try/{connection → integration/connection}/operation_mode_guards_try.rb +2 -2
  127. data/try/{connection → integration/connection}/pipeline_fallback_integration_try.rb +13 -13
  128. data/try/{core → integration/connection}/pools_try.rb +1 -1
  129. data/try/{connection → integration/connection}/responsibility_chain_tracking_try.rb +1 -1
  130. data/try/{connection → integration/connection}/transaction_fallback_integration_try.rb +1 -1
  131. data/try/{connection → integration/connection}/transaction_mode_permissive_try.rb +1 -1
  132. data/try/{connection → integration/connection}/transaction_mode_strict_try.rb +1 -1
  133. data/try/{connection → integration/connection}/transaction_mode_warn_try.rb +1 -1
  134. data/try/{connection → integration/connection}/transaction_modes_try.rb +1 -1
  135. data/try/{core → integration}/conventional_inheritance_try.rb +1 -1
  136. data/try/{core → integration}/create_method_try.rb +23 -23
  137. data/try/integration/cross_component_try.rb +1 -1
  138. data/try/integration/data_types/datatype_pipelines_try.rb +104 -0
  139. data/try/integration/data_types/datatype_transactions_try.rb +247 -0
  140. data/try/{core → integration}/database_consistency_try.rb +11 -8
  141. data/try/{core → integration}/familia_extended_try.rb +1 -1
  142. data/try/{core → integration}/familia_members_methods_try.rb +1 -1
  143. data/try/{models → integration/models}/customer_safe_dump_try.rb +6 -2
  144. data/try/{models → integration/models}/customer_try.rb +1 -1
  145. data/try/{models → integration/models}/datatype_base_try.rb +1 -1
  146. data/try/{models → integration/models}/familia_object_try.rb +2 -2
  147. data/try/{core → integration}/persistence_operations_try.rb +163 -11
  148. data/try/integration/relationships_persistence_round_trip_try.rb +441 -0
  149. data/try/{configuration → integration}/scenarios_try.rb +1 -1
  150. data/try/{core → integration}/secure_identifier_try.rb +1 -1
  151. data/try/{core → integration}/verifiable_identifier_try.rb +1 -1
  152. data/try/performance/benchmarks_try.rb +2 -2
  153. data/try/support/benchmarks/deserialization_benchmark.rb +180 -0
  154. data/try/support/benchmarks/deserialization_correctness_test.rb +237 -0
  155. data/try/{helpers → support/helpers}/test_helpers.rb +12 -3
  156. data/try/{core → unit/core}/autoloader_try.rb +1 -1
  157. data/try/{core → unit/core}/base_enhancements_try.rb +1 -9
  158. data/try/{core → unit/core}/connection_try.rb +1 -1
  159. data/try/{core → unit/core}/errors_try.rb +1 -1
  160. data/try/{core → unit/core}/extensions_try.rb +1 -1
  161. data/try/unit/core/familia_logger_try.rb +110 -0
  162. data/try/{core → unit/core}/familia_try.rb +1 -1
  163. data/try/{core → unit/core}/middleware_try.rb +41 -1
  164. data/try/{core → unit/core}/settings_try.rb +1 -1
  165. data/try/{core → unit/core}/time_utils_try.rb +1 -1
  166. data/try/{core → unit/core}/tools_try.rb +1 -1
  167. data/try/{core → unit/core}/utils_try.rb +17 -14
  168. data/try/{data_types → unit/data_types}/boolean_try.rb +2 -2
  169. data/try/{data_types → unit/data_types}/counter_try.rb +1 -1
  170. data/try/{data_types → unit/data_types}/datatype_base_try.rb +1 -1
  171. data/try/{data_types → unit/data_types}/hash_try.rb +1 -1
  172. data/try/{data_types → unit/data_types}/list_try.rb +1 -1
  173. data/try/{data_types → unit/data_types}/lock_try.rb +1 -1
  174. data/try/{data_types → unit/data_types}/sorted_set_try.rb +1 -1
  175. data/try/{data_types → unit/data_types}/sorted_set_zadd_options_try.rb +1 -1
  176. data/try/{data_types → unit/data_types}/string_try.rb +2 -2
  177. data/try/{data_types → unit/data_types}/unsortedset_try.rb +1 -1
  178. data/try/{horreum → unit/horreum}/auto_indexing_on_save_try.rb +33 -17
  179. data/try/unit/horreum/automatic_index_validation_try.rb +253 -0
  180. data/try/{horreum → unit/horreum}/base_try.rb +4 -4
  181. data/try/{horreum → unit/horreum}/class_methods_try.rb +3 -3
  182. data/try/{horreum → unit/horreum}/commands_try.rb +1 -1
  183. data/try/{horreum → unit/horreum}/defensive_initialization_try.rb +1 -1
  184. data/try/{horreum → unit/horreum}/destroy_related_fields_cleanup_try.rb +1 -1
  185. data/try/{horreum → unit/horreum}/enhanced_conflict_handling_try.rb +1 -1
  186. data/try/{horreum → unit/horreum}/field_categories_try.rb +27 -18
  187. data/try/{horreum → unit/horreum}/field_definition_try.rb +1 -1
  188. data/try/{horreum → unit/horreum}/initialization_try.rb +3 -3
  189. data/try/unit/horreum/json_type_preservation_try.rb +248 -0
  190. data/try/{horreum → unit/horreum}/relations_try.rb +5 -5
  191. data/try/{horreum → unit/horreum}/serialization_persistent_fields_try.rb +24 -18
  192. data/try/{horreum → unit/horreum}/serialization_try.rb +6 -6
  193. data/try/{horreum → unit/horreum}/settings_try.rb +1 -1
  194. data/try/unit/horreum/unique_index_edge_cases_try.rb +376 -0
  195. data/try/unit/horreum/unique_index_guard_validation_try.rb +281 -0
  196. data/try/{refinements → unit/refinements}/dear_json_array_methods_try.rb +1 -1
  197. data/try/{refinements → unit/refinements}/dear_json_hash_methods_try.rb +1 -1
  198. data/try/{refinements → unit/refinements}/time_literals_numeric_methods_try.rb +1 -1
  199. data/try/{refinements → unit/refinements}/time_literals_string_methods_try.rb +1 -1
  200. metadata +147 -126
  201. data/lib/familia/distinguisher.rb +0 -85
  202. data/lib/familia/refinements/logger_trace.rb +0 -60
  203. data/try/refinements/logger_trace_methods_try.rb +0 -44
  204. /data/try/{debugging → support/debugging}/README.md +0 -0
  205. /data/try/{debugging → support/debugging}/cache_behavior_tracer.rb +0 -0
  206. /data/try/{debugging → support/debugging}/debug_aad_process.rb +0 -0
  207. /data/try/{debugging → support/debugging}/debug_concealed_internal.rb +0 -0
  208. /data/try/{debugging → support/debugging}/debug_concealed_reveal.rb +0 -0
  209. /data/try/{debugging → support/debugging}/debug_context_aad.rb +0 -0
  210. /data/try/{debugging → support/debugging}/debug_context_simple.rb +0 -0
  211. /data/try/{debugging → support/debugging}/debug_cross_context.rb +0 -0
  212. /data/try/{debugging → support/debugging}/debug_database_load.rb +0 -0
  213. /data/try/{debugging → support/debugging}/debug_encrypted_json_check.rb +0 -0
  214. /data/try/{debugging → support/debugging}/debug_encrypted_json_step_by_step.rb +0 -0
  215. /data/try/{debugging → support/debugging}/debug_exists_lifecycle.rb +0 -0
  216. /data/try/{debugging → support/debugging}/debug_field_decrypt.rb +0 -0
  217. /data/try/{debugging → support/debugging}/debug_fresh_cross_context.rb +0 -0
  218. /data/try/{debugging → support/debugging}/debug_load_path.rb +0 -0
  219. /data/try/{debugging → support/debugging}/debug_method_definition.rb +0 -0
  220. /data/try/{debugging → support/debugging}/debug_method_resolution.rb +0 -0
  221. /data/try/{debugging → support/debugging}/debug_minimal.rb +0 -0
  222. /data/try/{debugging → support/debugging}/debug_provider.rb +0 -0
  223. /data/try/{debugging → support/debugging}/debug_secure_behavior.rb +0 -0
  224. /data/try/{debugging → support/debugging}/debug_string_class.rb +0 -0
  225. /data/try/{debugging → support/debugging}/debug_test.rb +0 -0
  226. /data/try/{debugging → support/debugging}/debug_test_design.rb +0 -0
  227. /data/try/{debugging → support/debugging}/encryption_method_tracer.rb +0 -0
  228. /data/try/{debugging → support/debugging}/provider_diagnostics.rb +0 -0
  229. /data/try/{helpers → support/helpers}/test_cleanup.rb +0 -0
  230. /data/try/{memory → support/memory}/memory_basic_test.rb +0 -0
  231. /data/try/{memory → support/memory}/memory_detailed_test.rb +0 -0
  232. /data/try/{memory → support/memory}/memory_docker_ruby_dump.sh +0 -0
  233. /data/try/{memory → support/memory}/memory_search_for_string.rb +0 -0
  234. /data/try/{memory → support/memory}/test_actual_redactedstring_protection.rb +0 -0
  235. /data/try/{prototypes → support/prototypes}/atomic_saves_v1_context_proxy.rb +0 -0
  236. /data/try/{prototypes → support/prototypes}/atomic_saves_v2_connection_switching.rb +0 -0
  237. /data/try/{prototypes → support/prototypes}/atomic_saves_v3_connection_pool.rb +0 -0
  238. /data/try/{prototypes → support/prototypes}/atomic_saves_v4.rb +0 -0
  239. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v2_connection_switching_helpers.rb +0 -0
  240. /data/try/{prototypes → support/prototypes}/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  241. /data/try/{prototypes → support/prototypes}/pooling/README.md +0 -0
  242. /data/try/{prototypes → support/prototypes}/pooling/configurable_stress_test.rb +0 -0
  243. /data/try/{prototypes → support/prototypes}/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +0 -0
  244. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_metrics.rb +0 -0
  245. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_stress_test.rb +0 -0
  246. /data/try/{prototypes → support/prototypes}/pooling/lib/connection_pool_threading_models.rb +0 -0
  247. /data/try/{prototypes → support/prototypes}/pooling/lib/visualize_stress_results.rb +0 -0
  248. /data/try/{prototypes → support/prototypes}/pooling/pool_siege.rb +0 -0
  249. /data/try/{prototypes → support/prototypes}/pooling/run_stress_tests.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e760a3ff094446126c56a0c2b76fd5bed0f6ff64d8eed89580e19d98a5a9ba66
4
- data.tar.gz: 74545b0bbb8c89b06ffd385c1448a95a7808b168686a955155a004b91160dafa
3
+ metadata.gz: 4250fd7b94da275c6cfe9ebc7515e14fc6bead4bb25597aa5e433bf6c9368727
4
+ data.tar.gz: 50818f7fce2464d3a4349d4de6a1fd7992900e45d8774f6d6d40d047d71a1842
5
5
  SHA512:
6
- metadata.gz: cd9cd6c374f24859279125ef05199b0dfdac51f7cccdf2bcdf0489c7cca5126ac03d7cab1d54aa8021ce38b286203212514b2b412322ea151b8957fa16c9b445
7
- data.tar.gz: 3e44e06249e6daf715ce09b02643b8faf153c85fc2d8451d4cf56d77c5115e5e5fdb1b752acef3f1fdd46f7b7eb7f019213f22850ffe728ec51a05d98ecc5528
6
+ metadata.gz: bf19202fe2ae0176698fa3ed5b5bd5cb4c1536de2e091a0978d75d8d9964c05cf2285cbcbbdf2d8deb20500a00b83f4fc4d7091e80f08fcaa29840053788da4f
7
+ data.tar.gz: c7a5810d3c84cca3c8f51e7449d0049c13eae86300f4703b5af9a492329a1a1c6ff01142529351e705f527c4b4f1ad8d07b15e5280b956a3c5a4fa2971ff2b08
data/CHANGELOG.rst CHANGED
@@ -1,17 +1,129 @@
1
1
  CHANGELOG.rst
2
2
  =============
3
3
 
4
- All notable changes to Familia are documented here.
5
-
6
- The format is based on `Keep a
7
- Changelog <https://keepachangelog.com/en/1.1.0/>`__, and this project
8
- adheres to `Semantic
9
- Versioning <https://semver.org/spec/v2.0.0.html>`__.
4
+ The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`__, and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`__.
10
5
 
11
6
  .. raw:: html
12
7
 
13
8
  <!--scriv-insert-here-->
14
9
 
10
+ .. _changelog-2.0.0.pre19:
11
+
12
+ 2.0.0.pre19 — 2025-10-11
13
+ =========================
14
+
15
+ Added
16
+ -----
17
+
18
+ - **DataType Transaction and Pipeline Support** - DataType objects can now initiate transactions and pipelines independently, enabling atomic operations and batch command execution for both parent-owned and standalone DataType objects. `PR #160 <https://github.com/familia/familia/pull/160>`__. Key capabilities added:
19
+
20
+ * ``transaction`` and ``pipelined`` methods for atomic MULTI/EXEC operations and batched command execution on all DataType classes
21
+ * Connection chain pattern with Chain of Responsibility for DataType objects
22
+ * Two new connection handlers: ``ParentDelegationHandler`` for owned DataTypes and ``StandaloneConnectionHandler`` for independent DataTypes
23
+ * Enhanced ``direct_access`` method with automatic transaction/pipeline context detection
24
+ * Shared ``Familia::Connection::Behavior`` module extracting common connection functionality
25
+
26
+ - New error hierarchy with ``PersistenceError``, ``HorreumError``, ``CreationError``, and ``OptimisticLockError`` classes for better error categorization and handling
27
+ - ``watch``, ``unwatch``, and ``discard`` Redis commands for optimistic locking support
28
+ - Enhanced database command logging with structured format for pipelined and transaction operations
29
+ - ``save_fields`` method in Persistence module for selective field updates
30
+
31
+ Changed
32
+ -------
33
+
34
+ - **Connection Architecture Refactored** - The ``Horreum::Connection`` module now includes ``Familia::Connection::Behavior``, eliminating code duplication by sharing URI normalization and connection creation methods between Horreum and DataType. DataType objects with ``logical_database`` settings now return clean URIs without custom port information (e.g., ``redis://127.0.0.1/3`` instead of ``redis://127.0.0.1:2525/3``), ensuring consistent URI representation across the library.
35
+
36
+ - **BREAKING**: Renamed ``Management.create`` to ``create!`` to follow Rails conventions and indicate potential exceptions
37
+ - **BREAKING**: Updated ``save_if_not_exists`` to ``save_if_not_exists!`` with optimistic locking and automatic retry logic (up to 3 attempts)
38
+ - Improved ``save`` method to use single atomic transaction encompassing field updates, expiration setting, index updates, and instance collection management
39
+ - Enhanced ``delete!`` methods to work correctly within Redis transactions
40
+ - Updated timestamp fields (``created``, ``updated``) to use float values instead of integers for higher precision
41
+ - Refined log message formatting for better readability and debugging
42
+ - Removed deprecated Connection instance methods for Horreum models in favor of class-level database operations
43
+ - Clarified "pipelined" terminology throughout codebase (renamed from "pipeline" for consistency with Redis documentation)
44
+
45
+ Fixed
46
+ -----
47
+
48
+ - Resolved atomicity issues and race conditions in save operations by consolidating all related operations into single Redis transaction with proper watch/multi/exec pattern and optimistic locking
49
+ - Corrected transaction handling to ensure proper cleanup and error propagation
50
+
51
+ Documentation
52
+ -------------
53
+
54
+ - Added comprehensive parameter documentation for database command methods including return value specifications and usage examples
55
+
56
+ AI Assistance
57
+ -------------
58
+
59
+ This feature was implemented with AI assistance from Claude Sonnet 4.5, Opus 4.1 (Anthropic).
60
+
61
+ * Architectural design of the connection chain pattern and shared Behavior module
62
+ * Implementation of DataType-specific connection handlers (ParentDelegationHandler, StandaloneConnectionHandler) and comprehensive test coverage
63
+ * Error hierarchy design and transaction atomicity optimization
64
+ * Documentation enhancement and URI formatting debugging
65
+
66
+
67
+ .. _changelog-2.0.0.pre18:
68
+
69
+ 2.0.0.pre18 — 2025-10-05
70
+ ========================
71
+
72
+ Added
73
+ -----
74
+
75
+ - Added ``Familia.reconnect!`` method to refresh connection pools with current middleware configuration. This solves issues in test suites where middleware (like DatabaseLogger) is enabled after connection pools are created. The method clears the connection chain, increments the middleware version, and clears fiber-local connections, ensuring new connections include the latest middleware. See ``lib/familia/connection/middleware.rb:81-117``.
76
+
77
+ Changed
78
+ -------
79
+
80
+ - **BREAKING**: Implemented type-preserving JSON serialization for Horreum field values. Non-string values (Integer, Boolean, Float, nil, Hash, Array) are JSON-encoded for storage and JSON-decoded on retrieval. **Strings are stored as-is without JSON encoding** to avoid double-quoting and maintain Redis baseline simplicity. Type preservation is achieved through smart deserialization: values that parse as JSON restore to their original types, otherwise remain as strings.
81
+
82
+ - **BREAKING**: Changed default Hash key format from symbols to strings throughout the codebase (``symbolize: false`` default). This eliminates ambiguity with HTTP request parameters and IndifferentHash-style implementations, providing strict adherence to JSON parsing rules and avoiding key duplication issues.
83
+
84
+ - **BREAKING**: Fixed ``initialize_with_keyword_args`` to properly handle ``false`` and ``0`` values during object initialization. Previously, falsy values were incorrectly skipped due to truthiness checks. Now uses explicit nil checking with ``fetch`` to preserve all non-nil values including ``false`` and ``0``.
85
+
86
+ - **String serialization now uses JSON encoding**: All string values are JSON-encoded during storage (wrapped in quotes) for consistent type preservation. The lenient deserializer handles both new JSON-encoded strings and legacy plain strings automatically. PR #152
87
+
88
+ Removed
89
+ -------
90
+
91
+ - **BREAKING**: Removed ``dump_method`` and ``load_method`` configuration options from ``Familia::Base`` and ``Familia::Horreum::Definition``. JSON serialization is now hard-coded for consistency and type safety. Custom serialization methods are no longer supported.
92
+
93
+ Fixed
94
+ -----
95
+
96
+ - Fixed type coercion bugs where Integer fields (e.g., ``age: 35``) became Strings (``"35"``) and Boolean fields (e.g., ``active: true``) became Strings (``"true"``) after database round-trips. All primitive types now maintain their original types through ``find_by_dbkey``, ``refresh!``, and ``batch_update`` operations.
97
+
98
+ - Fixed ``deserialize_value`` to return all JSON-parsed types instead of filtering to Hash/Array only. This enables proper deserialization of primitive types (Integer, Boolean, Float, String) from Redis storage.
99
+
100
+ - Added JSON deserialization in ``find_by_dbkey`` using existing ``initialize_with_keyword_args_deserialize_value`` helper method to maintain DRY principles and ensure loaded objects receive properly typed field values rather than raw Redis strings.
101
+
102
+ - Optimized serialization to avoid double-encoding strings - strings stored directly in Redis as-is, only non-string types use JSON encoding. This reduces storage overhead and maintains Redis's string baseline semantics.
103
+
104
+ - Fixed encrypted fields with ``category: :encrypted`` appearing in ``to_h()`` output. These fields now correctly set ``loggable: false`` to prevent accidental exposure in logs, APIs, or external interfaces. PR #152
105
+
106
+ - Fixed middleware registration to only set ``@middleware_registered`` flag when middleware is actually enabled and registered. Previously, calling ``create_dbclient`` before enabling middleware would set the flag to ``true`` without registering anything, preventing later middleware enablement from working. The fix ensures ``register_middleware_once`` only sets the flag after successful registration. See ``lib/familia/connection/middleware.rb:124-146``.
107
+
108
+ Security
109
+ --------
110
+
111
+ - Encrypted fields defined via ``field :name, category: :encrypted`` now properly excluded from ``to_h()`` serialization, matching the security behavior of ``encrypted_field``. PR #152
112
+
113
+ Documentation
114
+ -------------
115
+
116
+ - Added comprehensive type preservation test suite (``try/unit/horreum/json_type_preservation_try.rb``) with 30 test cases covering Integer, Boolean, String, Float, Hash, Array, nested structures, nil handling, empty strings, zero values, round-trip consistency, ``batch_update``, and ``refresh!`` operations.
117
+
118
+ AI Assistance
119
+ -------------
120
+
121
+ - Claude Code (claude-sonnet-4-5) provided implementation guidance, identified the ``initialize_with_keyword_args`` falsy value bug, wrote comprehensive test suite, and coordinated multi-file changes across serialization, management, and base modules.
122
+
123
+ - Issue analysis, implementation guidance, test verification, and documentation for JSON serialization changes and encrypted field security fix.
124
+
125
+ - Claude Code (Sonnet 4.5) provided architecture analysis, implementation design, and identified critical issues through the second-opinion agent. Key contributions included recommending the simplified approach without pool shutdown lifecycle management, identifying the race condition risk in clearing ``@middleware_registered``, and suggesting the use of natural pool aging instead of explicit shutdown.
126
+
15
127
  .. _changelog-2.0.0.pre17:
16
128
 
17
129
  2.0.0.pre17 — 2025-10-03
data/CLAUDE.md CHANGED
@@ -105,30 +105,55 @@ class User < Familia::Horreum
105
105
  end
106
106
  ```
107
107
 
108
- **Good - use the `init` hook instead:**
108
+ **Good - use the `init` hook to apply defaults (use `||=` not `=`):**
109
109
  ```ruby
110
110
  class User < Familia::Horreum
111
- def init(email = nil)
112
- @email = email # Called after super, related fields work
111
+ field :objid
112
+ field :email
113
+
114
+ # Called after Horreum sets fields from kwargs
115
+ # IMPORTANT: Use ||= to apply defaults, not = to override
116
+ def init
117
+ @objid ||= SecureRandom.uuid # Apply default only if not already set
118
+ _run_post_init_hooks # Additional setup logic
113
119
  end
114
120
  end
121
+
122
+ # This works correctly:
123
+ user = User.new(email: 'test@example.com')
124
+ user.objid # → generated UUID (applied by init)
125
+ user.email # → 'test@example.com' (set by Horreum from kwargs)
115
126
  ```
116
127
 
117
- **Good - call super explicitly:**
128
+ **Okay - if absolutely necessary, override and call super explicitly:**
118
129
  ```ruby
119
130
  class User < Familia::Horreum
120
131
  def initialize(email = nil, **kwargs)
121
- super(**kwargs) # Related fields initialized
122
- @email = email
132
+ super # Initializes related fields here and also calls init
133
+ @email ||= generate_email if email.nil?
123
134
  end
124
135
  end
125
136
  ```
126
137
 
127
- **Why this matters**: Familia's `initialize` method calls `initialize_relatives` to set up DataType objects (lists, sets, etc.). Without calling `super`, these objects remain nil and you'll get helpful errors pointing to the missing super call.
138
+ **Why this matters**: Familia's `initialize` method processes kwargs FIRST (setting fields), then calls `initialize_relatives` (setting up DataType objects), then calls your `init` hook. By the time `init` runs, kwargs have already been consumed and fields are set.
139
+
140
+ **The ||= Pattern Explained**:
141
+ ```ruby
142
+ # WRONG - overwrites what Horreum already set
143
+ def init
144
+ @email = generate_email # Overwrites the correct value
145
+ end
146
+
147
+ # RIGHT - applies default only if not already set
148
+ def init
149
+ @email ||= email # Preserves value Horreum set from kwargs
150
+ @email ||= 'default@example.com' # Apply fallback default if still nil
151
+ end
152
+ ```
128
153
 
129
154
  **When to use each approach:**
130
- - **Use `init` hook** (preferred): For simple initialization logic that doesn't need to intercept constructor arguments. The `init` method is called automatically after `super` with the same arguments passed to `new`.
131
- - **Use explicit `super`**: When you need full control over initialization order or need to transform arguments before passing to parent. Remember to pass `**kwargs` to preserve keyword argument handling.
155
+ - **Use `init` hook with `||=`** (preferred): Apply defaults, run validations, setup callbacks - any logic that should run after field initialization. Follows standard ORM lifecycle hook patterns.
156
+ - **Use explicit `super`**: Only when you need to intercept or transform arguments before Horreum processes them (rare).
132
157
 
133
158
  **DataType Definition**: Use class methods to define keystore database-backed attributes:
134
159
  ```ruby
@@ -154,8 +179,15 @@ end
154
179
 
155
180
  ### Important Implementation Notes
156
181
 
157
- **Field Initialization**: Objects can be initialized with positional args (brittle) or keyword args (robust). Keyword args are recommended.
158
- **Serialization**: Uses JSON by default but supports custom `serialize_value`/`deserialize_value` methods.
182
+ **Field Initialization**: Objects can be initialized with positional args (brittle) or keyword args (robust). Keyword args are recommended. All non-nil values including `false` and `0` are preserved during initialization.
183
+
184
+ **Serialization**: All field values are JSON-encoded for storage and JSON-decoded on retrieval to preserve Ruby types (Integer, Boolean, String, Float, Hash, Array, nil). This ensures type preservation across the Redis storage boundary. For example:
185
+ - `age: 35` (Integer) stores as `"35"` in Redis and loads back as Integer `35`
186
+ - `active: true` (Boolean) stores as `"true"` in Redis and loads back as Boolean `true`
187
+ - `metadata: {key: "value"}` (Hash) stores as JSON and loads back as Hash with proper types
188
+
159
189
  **Database Key Generation**: Automatic key generation using class name, identifier, and field/type names (aka dbkey). Pattern: `classname:identifier:fieldname`
190
+
160
191
  **Memory Efficiency**: Only non-nil values are stored in keystore database to optimize memory usage.
192
+
161
193
  **Thread Safety**: Data types are frozen after instantiation to ensure immutability.
data/Gemfile CHANGED
@@ -17,10 +17,10 @@ group :development, :test do
17
17
  gem 'irb', '~> 1.15.2', require: false
18
18
  gem 'redcarpet', require: false
19
19
  gem 'reek', require: false
20
- gem 'rubocop', require: false
20
+ gem 'rubocop', '~> 1.81.1', require: false
21
21
  gem 'rubocop-performance', require: false
22
22
  gem 'rubocop-thread_safety', require: false
23
- gem 'solargraph', require: false
23
+ gem 'ruby-lsp', require: false
24
24
  gem 'yard', '~> 0.9', require: false
25
25
  end
26
26
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- familia (2.0.0.pre17)
4
+ familia (2.0.0.pre19)
5
5
  benchmark (~> 0.4)
6
6
  connection_pool (~> 2.5)
7
7
  csv (~> 3.3)
@@ -15,7 +15,6 @@ GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
17
  ast (2.4.3)
18
- backport (1.2.0)
19
18
  base64 (0.3.0)
20
19
  benchmark (0.4.1)
21
20
  bigdecimal (3.2.3)
@@ -64,23 +63,11 @@ GEM
64
63
  pp (>= 0.6.0)
65
64
  rdoc (>= 4.0.0)
66
65
  reline (>= 0.4.2)
67
- jaro_winkler (1.6.1)
68
- json (2.15.0)
69
- kramdown (2.5.1)
70
- rexml (>= 3.3.9)
71
- kramdown-parser-gfm (1.1.0)
72
- kramdown (~> 2.0)
66
+ json (2.15.1)
73
67
  language_server-protocol (3.17.0.5)
74
68
  lint_roller (1.1.0)
75
69
  logger (1.7.0)
76
- mini_portile2 (2.8.9)
77
70
  minitest (5.25.5)
78
- nokogiri (1.18.10)
79
- mini_portile2 (~> 2.8.2)
80
- racc (~> 1.4)
81
- nokogiri (1.18.10-arm64-darwin)
82
- racc (~> 1.4)
83
- observer (0.1.2)
84
71
  oj (3.16.11)
85
72
  bigdecimal (>= 3.0)
86
73
  ostruct (>= 0.2)
@@ -94,7 +81,7 @@ GEM
94
81
  pp (0.6.2)
95
82
  prettyprint
96
83
  prettyprint (0.2.0)
97
- prism (1.5.1)
84
+ prism (1.5.2)
98
85
  psych (5.2.6)
99
86
  date
100
87
  stringio
@@ -121,8 +108,6 @@ GEM
121
108
  regexp_parser (2.11.3)
122
109
  reline (0.6.2)
123
110
  io-console (~> 0.5)
124
- reverse_markdown (3.0.0)
125
- nokogiri
126
111
  rexml (3.4.1)
127
112
  rspec (3.13.1)
128
113
  rspec-core (~> 3.13.0)
@@ -159,34 +144,15 @@ GEM
159
144
  lint_roller (~> 1.1)
160
145
  rubocop (~> 1.72, >= 1.72.1)
161
146
  rubocop-ast (>= 1.44.0, < 2.0)
147
+ ruby-lsp (0.26.1)
148
+ language_server-protocol (~> 3.17.0)
149
+ prism (>= 1.2, < 2.0)
150
+ rbs (>= 3, < 5)
162
151
  ruby-prof (1.7.2)
163
152
  base64
164
153
  ruby-progressbar (1.13.0)
165
- solargraph (0.57.0)
166
- backport (~> 1.2)
167
- benchmark (~> 0.4)
168
- bundler (~> 2.0)
169
- diff-lcs (~> 1.4)
170
- jaro_winkler (~> 1.6, >= 1.6.1)
171
- kramdown (~> 2.3)
172
- kramdown-parser-gfm (~> 1.1)
173
- logger (~> 1.6)
174
- observer (~> 0.1)
175
- ostruct (~> 0.6)
176
- parser (~> 3.0)
177
- prism (~> 1.4)
178
- rbs (>= 3.6.1, <= 4.0.0.dev.4)
179
- reverse_markdown (~> 3.0)
180
- rubocop (~> 1.76)
181
- thor (~> 1.0)
182
- tilt (~> 2.0)
183
- yard (~> 0.9, >= 0.9.24)
184
- yard-activesupport-concern (~> 0.0)
185
- yard-solargraph (~> 0.1)
186
154
  stackprof (0.2.27)
187
155
  stringio (3.1.7)
188
- thor (1.4.0)
189
- tilt (2.6.1)
190
156
  timecop (0.9.10)
191
157
  tryouts (3.6.0)
192
158
  concurrent-ruby (~> 1.0)
@@ -205,10 +171,6 @@ GEM
205
171
  unicode-emoji (4.1.0)
206
172
  uri-valkey (1.4.0)
207
173
  yard (0.9.37)
208
- yard-activesupport-concern (0.0.1)
209
- yard (>= 0.8)
210
- yard-solargraph (0.1.0)
211
- yard (~> 0.9)
212
174
  zeitwerk (2.7.3)
213
175
 
214
176
  PLATFORMS
@@ -223,11 +185,11 @@ DEPENDENCIES
223
185
  rbnacl (~> 7.1, >= 7.1.1)
224
186
  redcarpet
225
187
  reek
226
- rubocop
188
+ rubocop (~> 1.81.1)
227
189
  rubocop-performance
228
190
  rubocop-thread_safety
191
+ ruby-lsp
229
192
  ruby-prof
230
- solargraph
231
193
  stackprof
232
194
  timecop
233
195
  tryouts (~> 3.6.0)
data/README.md CHANGED
@@ -280,6 +280,7 @@ Flower.multiget("prose", "tulip", "daisy")
280
280
 
281
281
  ### Transactional Operations
282
282
 
283
+ **Horreum Model Transactions:**
283
284
  ```ruby
284
285
  user.transaction do |conn|
285
286
  conn.set("user:#{user.id}:status", "active")
@@ -287,6 +288,44 @@ user.transaction do |conn|
287
288
  end
288
289
  ```
289
290
 
291
+ **DataType Transactions** (standalone or parent-owned):
292
+ ```ruby
293
+ # Recommended: Use DataType methods for clean, automatic key handling
294
+ user.scores.transaction do
295
+ user.scores.add('level1', 100)
296
+ user.scores.add('level2', 200)
297
+ end
298
+
299
+ # Standalone DataType transaction (e.g., session storage)
300
+ session_key = Familia::StringKey.new('session:abc123')
301
+ session_key.transaction do
302
+ session_key.set(session_data)
303
+ session_key.expire(3600) # Atomic: both succeed or both fail
304
+ end
305
+
306
+ # Advanced: Connection available for low-level Redis commands
307
+ user.scores.transaction do |conn|
308
+ conn.zadd(user.scores.dbkey, 100, 'level1')
309
+ conn.hset(user.profile.dbkey, 'status', 'active')
310
+ end
311
+ ```
312
+
313
+ **Pipeline Operations** (batch commands for performance):
314
+ ```ruby
315
+ # Recommended: Use DataType methods
316
+ leaderboard.pipelined do
317
+ leaderboard.add('player1', 500)
318
+ leaderboard.add('player2', 600)
319
+ leaderboard.size
320
+ end
321
+
322
+ # Advanced: Raw Redis commands for fine-grained control
323
+ leaderboard.pipelined do |conn|
324
+ conn.zadd(leaderboard.dbkey, 500, 'player1')
325
+ conn.zadd(leaderboard.dbkey, 600, 'player2')
326
+ end
327
+ ```
328
+
290
329
  ### Advanced Patterns
291
330
 
292
331
  **Time-based Expiration:**
@@ -438,6 +477,19 @@ Contributions are welcome! Please feel free to submit a Pull Request.
438
477
  4. Push to the branch (`git push origin feature/amazing-feature`)
439
478
  5. Open a Pull Request
440
479
 
480
+ ### PR Compliance Checks
481
+
482
+ Pull requests are automatically reviewed by [Qodo Merge](https://qodo.ai) with compliance checks for:
483
+ - **Error Handling** - External API calls and database operations must have proper error handling
484
+ - **Test Coverage** - New features must include tests using the Tryouts framework
485
+ - **Changelog Fragments** - User-facing changes should include a changelog entry
486
+ - **Documentation** - API changes must update documentation
487
+ - **Backward Compatibility** - Breaking changes must be documented
488
+ - **Thread Safety** - Shared state must be properly synchronized
489
+ - **Database Key Naming** - Keys must follow Familia conventions
490
+
491
+ See [docs/qodo-merge-compliance.md](docs/qodo-merge-compliance.md) for details.
492
+
441
493
  ## License
442
494
 
443
495
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
data/bin/irb CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/bin/bash
2
2
 
3
- irb -Ilib -r familia -Itry -r helpers/test_helpers
3
+ irb -Ilib -r familia -Itry -r support/helpers/test_helpers
@@ -0,0 +1,91 @@
1
+ .. Added
2
+ .. -----
3
+ .. New features and capabilities that have been added.
4
+
5
+ .. Changed
6
+ .. -------
7
+ .. Changes to existing functionality.
8
+
9
+ .. Deprecated
10
+ .. ----------
11
+ .. Soon-to-be removed features.
12
+
13
+ .. Removed
14
+ .. -------
15
+ .. Now removed features.
16
+
17
+ .. Fixed
18
+ .. -----
19
+ .. Bug fixes.
20
+
21
+ .. Security
22
+ .. --------
23
+ .. Security-related improvements.
24
+
25
+ Added
26
+ -----
27
+
28
+ - **DataType Transaction and Pipeline Support** - DataType objects can now initiate transactions and pipelines independently, enabling atomic operations and batch command execution for both parent-owned and standalone DataType objects. `PR #159 <https://github.com/familia/familia/pull/159>`_
29
+
30
+ Key capabilities added:
31
+
32
+ * ``transaction`` method for atomic MULTI/EXEC operations on all DataType classes
33
+ * ``pipelined`` method for batched command execution on all DataType classes
34
+ * Connection chain pattern with Chain of Responsibility for DataType objects
35
+ * Two new connection handlers: ``ParentDelegationHandler`` for owned DataTypes and ``StandaloneConnectionHandler`` for independent DataTypes
36
+ * Enhanced ``direct_access`` method with automatic transaction/pipeline context detection
37
+ * Shared ``Familia::Connection::Behavior`` module extracting common connection functionality
38
+
39
+ This enhancement addresses a critical gap where standalone DataType objects could not guarantee atomicity across multiple operations. A prime example is session storage implementations (similar to Rack::Session stores) where setting session data and expiration must be atomic to prevent memory leaks or security issues. Both parent-owned DataTypes (delegating to parent Horreum objects) and standalone DataTypes now support the full transaction and pipeline API.
40
+
41
+ Example usage:
42
+
43
+ .. code-block:: ruby
44
+
45
+ # Recommended: Use DataType methods for clean, key-free syntax
46
+ # Parent-owned DataType transaction
47
+ user.scores.transaction do
48
+ user.scores.add('level1', 100)
49
+ user.scores.add('level2', 200)
50
+ end
51
+
52
+ # Standalone DataType transaction (e.g., session storage)
53
+ session_store = Familia::StringKey.new('session:abc123')
54
+ session_store.transaction do
55
+ session_store.set(session_data)
56
+ session_store.update_expiration(expiration: 3600)
57
+ end
58
+
59
+ # Pipeline for performance optimization
60
+ leaderboard.pipelined do
61
+ leaderboard.add('player1', 500)
62
+ leaderboard.add('player2', 600)
63
+ leaderboard.size
64
+ end
65
+
66
+ # Advanced: Connection available for low-level Redis commands when needed
67
+ user.scores.transaction do |conn|
68
+ conn.zadd(user.scores.dbkey, 100, 'level1')
69
+ conn.hset(user.profile.dbkey, 'status', 'active')
70
+ end
71
+
72
+ Changed
73
+ -------
74
+
75
+ - **DataType URI Construction** - DataType objects with ``logical_database`` settings now return clean URIs without custom port information (e.g., ``redis://127.0.0.1/3`` instead of ``redis://127.0.0.1:2525/3``), ensuring consistent URI representation across the library.
76
+
77
+ - **Horreum::Connection Refactored** - The ``Horreum::Connection`` module now includes ``Familia::Connection::Behavior``, eliminating code duplication by sharing URI normalization and connection creation methods between Horreum and DataType. This refactoring improves maintainability while preserving all existing functionality.
78
+
79
+ AI Assistance
80
+ -------------
81
+
82
+ This feature was implemented with significant AI assistance from Claude (Anthropic). The AI helped with:
83
+
84
+ * Architectural design of the connection chain pattern for DataType objects
85
+ * Implementation of the shared Behavior module to extract common functionality
86
+ * Creation of DataType-specific connection handlers (ParentDelegationHandler, StandaloneConnectionHandler)
87
+ * Comprehensive test coverage including transaction and pipeline integration tests
88
+ * Documentation and changelog preparation
89
+ * Debugging and fixing URI formatting edge cases
90
+
91
+ The implementation preserves backward compatibility (all 2,216 existing tests pass) while adding 27 new tests specifically for DataType transaction and pipeline support.
@@ -0,0 +1,30 @@
1
+ .. A new scriv changelog fragment.
2
+ ..
3
+ .. Uncomment the section that is right (remove the leading dots).
4
+ .. For top level release notes, leave all the headers commented out.
5
+ ..
6
+ Added
7
+ -----
8
+
9
+ - Automatic validation in ``add_to_*`` methods for instance-scoped unique indexes. Previously required manual ``guard_unique_*!`` call before adding to index; now validation happens automatically with clear error messages on duplicate detection.
10
+
11
+ - Transaction detection in ``save()`` method. Raises ``Familia::OperationModeError`` when ``save()`` is called within an existing transaction, since unique index guards need to read current values which is not possible inside MULTI/EXEC blocks.
12
+
13
+ Changed
14
+ -------
15
+
16
+ - Instance-scoped unique index ``add_to_*`` methods now automatically validate uniqueness before adding to parent's index. This matches modern ORM expectations where constraint validation happens implicitly during mutation operations.
17
+
18
+ Documentation
19
+ -------------
20
+
21
+ - Enhanced ``save()`` method documentation to explain transaction restrictions and unique index validation flow.
22
+
23
+ - Updated ``UniqueIndexGenerators`` documentation to clarify that ``add_to_*`` methods perform automatic validation.
24
+
25
+ - Added comprehensive test suite (21 test cases) demonstrating automatic validation behavior, transaction detection, and error handling patterns.
26
+
27
+ AI Assistance
28
+ -------------
29
+
30
+ - Claude Sonnet 4.5 assisted with implementation design, test coverage, and documentation for automatic unique index validation and transaction detection features.
@@ -0,0 +1,13 @@
1
+
2
+ Changed
3
+ -------
4
+
5
+ - **IndexingRelationship**: Added explicit ``:within`` field to preserve the original DSL parameter, replacing brittle ``target_class`` equality checks with clearer ``within.nil?`` checks. This makes the distinction between class-level and instance-scoped indexes more explicit and prevents potential issues with inheritance scenarios.
6
+
7
+ AI Assistance
8
+ -------------
9
+
10
+ - Design review and architectural analysis by Claude Code (Sonnet 4.5) via second-opinion agent, identifying brittleness in class comparison logic and recommending explicit storage of the ``within`` parameter.
11
+ - Implementation of the ``within`` field addition across IndexingRelationship, generators, and usage sites by Claude Code.
12
+ - All tests verified passing with no behavioral changes.
13
+ ..
@@ -0,0 +1,26 @@
1
+ .. Internal terminology refactoring for indexing relationships
2
+
3
+ Changed
4
+ -------
5
+
6
+ - **Indexing terminology refactoring**: Renamed internal field ``target_class`` to ``scope_class`` throughout the indexing system to better reflect the semantic role. The ``within:`` parameter in index declarations refers to a "scope" that provides a uniqueness boundary, not a "target" or "parent" relationship. This change affects internal code, comments, and documentation but has no user-facing API impact.
7
+
8
+ - Renamed ``IndexingRelationship.target_class`` to ``scope_class``
9
+ - Updated method parameter names from ``target_instance`` to ``scope_instance``
10
+ - Replaced "parent" terminology with "scope" in comments and documentation
11
+ - Updated cheatsheets to reflect correct terminology
12
+
13
+ **Rationale**: The term "target" created semantic confusion because it has different meanings in participation relationships (where objects target a collection owner) versus indexing relationships (where objects use a scope for uniqueness). The term "parent" was misleading because it implied an ownership relationship that doesn't exist. "Scope" accurately describes the role: Company provides the scope within which badge_number must be unique.
14
+
15
+ Documentation
16
+ -------------
17
+
18
+ - Updated indexing and relationships cheatsheets with improved terminology explanations
19
+ - Added explicit clarification of scope vs target vs parent semantics
20
+
21
+ AI Assistance
22
+ -------------
23
+
24
+ - Claude Code (Sonnet 4.5) provided second-opinion analysis on terminology confusion
25
+ - Assisted with systematic refactoring of variable names, comments, and documentation
26
+ - Helped identify all occurrences requiring updates across codebase and tests