familia 2.0.0.pre18 → 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.
- checksums.yaml +4 -4
 - data/CHANGELOG.rst +58 -6
 - data/CLAUDE.md +34 -9
 - data/Gemfile +2 -2
 - data/Gemfile.lock +9 -47
 - data/README.md +39 -0
 - data/changelog.d/20251011_012003_delano_159_datatype_transaction_pipeline_support.rst +91 -0
 - data/changelog.d/20251011_203905_delano_next.rst +30 -0
 - data/changelog.d/20251011_212633_delano_next.rst +13 -0
 - data/changelog.d/20251011_221253_delano_next.rst +26 -0
 - data/docs/guides/feature-expiration.md +18 -18
 - data/docs/migrating/v2.0.0-pre19.md +197 -0
 - data/examples/datatype_standalone.rb +281 -0
 - data/lib/familia/connection/behavior.rb +252 -0
 - data/lib/familia/connection/handlers.rb +95 -0
 - data/lib/familia/connection/operation_core.rb +1 -1
 - data/lib/familia/connection/{pipeline_core.rb → pipelined_core.rb} +2 -2
 - data/lib/familia/connection/transaction_core.rb +7 -9
 - data/lib/familia/connection.rb +3 -2
 - data/lib/familia/data_type/connection.rb +151 -7
 - data/lib/familia/data_type/database_commands.rb +7 -4
 - data/lib/familia/data_type/serialization.rb +4 -0
 - data/lib/familia/data_type/types/hashkey.rb +1 -1
 - data/lib/familia/errors.rb +51 -14
 - data/lib/familia/features/expiration/extensions.rb +8 -10
 - data/lib/familia/features/expiration.rb +19 -19
 - data/lib/familia/features/relationships/indexing/multi_index_generators.rb +39 -38
 - data/lib/familia/features/relationships/indexing/unique_index_generators.rb +115 -43
 - data/lib/familia/features/relationships/indexing.rb +37 -42
 - data/lib/familia/features/relationships/indexing_relationship.rb +14 -4
 - data/lib/familia/field_type.rb +2 -1
 - data/lib/familia/horreum/connection.rb +11 -35
 - data/lib/familia/horreum/database_commands.rb +129 -10
 - data/lib/familia/horreum/definition.rb +2 -1
 - data/lib/familia/horreum/management.rb +21 -15
 - data/lib/familia/horreum/persistence.rb +190 -66
 - data/lib/familia/horreum/serialization.rb +3 -0
 - data/lib/familia/horreum/utils.rb +0 -8
 - data/lib/familia/horreum.rb +31 -12
 - data/lib/familia/logging.rb +2 -5
 - data/lib/familia/settings.rb +7 -7
 - data/lib/familia/version.rb +1 -1
 - data/lib/middleware/database_logger.rb +76 -5
 - data/try/edge_cases/string_coercion_try.rb +4 -4
 - data/try/features/expiration/expiration_try.rb +1 -1
 - data/try/features/relationships/indexing_try.rb +28 -4
 - data/try/features/relationships/relationships_api_changes_try.rb +4 -4
 - data/try/integration/connection/fiber_context_preservation_try.rb +3 -3
 - data/try/integration/connection/operation_mode_guards_try.rb +1 -1
 - data/try/integration/connection/pipeline_fallback_integration_try.rb +12 -12
 - data/try/integration/create_method_try.rb +22 -22
 - data/try/integration/data_types/datatype_pipelines_try.rb +104 -0
 - data/try/integration/data_types/datatype_transactions_try.rb +247 -0
 - data/try/integration/models/customer_safe_dump_try.rb +5 -1
 - data/try/integration/models/familia_object_try.rb +1 -1
 - data/try/integration/persistence_operations_try.rb +162 -10
 - data/try/unit/data_types/boolean_try.rb +1 -1
 - data/try/unit/data_types/string_try.rb +1 -1
 - data/try/unit/horreum/auto_indexing_on_save_try.rb +32 -16
 - data/try/unit/horreum/automatic_index_validation_try.rb +253 -0
 - data/try/unit/horreum/base_try.rb +1 -1
 - data/try/unit/horreum/class_methods_try.rb +2 -2
 - data/try/unit/horreum/initialization_try.rb +1 -1
 - data/try/unit/horreum/relations_try.rb +4 -4
 - data/try/unit/horreum/serialization_try.rb +2 -2
 - data/try/unit/horreum/unique_index_edge_cases_try.rb +376 -0
 - data/try/unit/horreum/unique_index_guard_validation_try.rb +281 -0
 - metadata +14 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 4250fd7b94da275c6cfe9ebc7515e14fc6bead4bb25597aa5e433bf6c9368727
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 50818f7fce2464d3a4349d4de6a1fd7992900e45d8774f6d6d40d047d71a1842
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: bf19202fe2ae0176698fa3ed5b5bd5cb4c1536de2e091a0978d75d8d9964c05cf2285cbcbbdf2d8deb20500a00b83f4fc4d7091e80f08fcaa29840053788da4f
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: c7a5810d3c84cca3c8f51e7449d0049c13eae86300f4703b5af9a492329a1a1c6ff01142529351e705f527c4b4f1ad8d07b15e5280b956a3c5a4fa2971ff2b08
         
     | 
    
        data/CHANGELOG.rst
    CHANGED
    
    | 
         @@ -1,17 +1,69 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            CHANGELOG.rst
         
     | 
| 
       2 
2 
     | 
    
         
             
            =============
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       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 
     | 
    
         
            +
             
     | 
| 
       15 
67 
     | 
    
         
             
            .. _changelog-2.0.0.pre18:
         
     | 
| 
       16 
68 
     | 
    
         | 
| 
       17 
69 
     | 
    
         
             
            2.0.0.pre18 — 2025-10-05
         
     | 
    
        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  
     | 
| 
      
 108 
     | 
    
         
            +
            **Good - use the `init` hook to apply defaults (use `||=` not `=`):**
         
     | 
| 
       109 
109 
     | 
    
         
             
            ```ruby
         
     | 
| 
       110 
110 
     | 
    
         
             
            class User < Familia::Horreum
         
     | 
| 
       111 
     | 
    
         
            -
               
     | 
| 
       112 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
            ** 
     | 
| 
      
 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 
     | 
| 
       122 
     | 
    
         
            -
                @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`  
     | 
| 
      
 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 
     | 
| 
       131 
     | 
    
         
            -
            - **Use explicit `super`**:  
     | 
| 
      
 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
         
     | 
    
        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 ' 
     | 
| 
      
 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. 
     | 
| 
      
 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 
     | 
    
         
            -
                 
     | 
| 
       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. 
     | 
| 
      
 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:**
         
     | 
| 
         @@ -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
         
     | 
| 
         @@ -66,7 +66,7 @@ session.default_expiration  # => 900.0 
     | 
|
| 
       66 
66 
     | 
    
         
             
            session.update_expiration  # Uses instance expiration (15 minutes)
         
     | 
| 
       67 
67 
     | 
    
         | 
| 
       68 
68 
     | 
    
         
             
            # Or specify expiration inline
         
     | 
| 
       69 
     | 
    
         
            -
            session.update_expiration( 
     | 
| 
      
 69 
     | 
    
         
            +
            session.update_expiration(expiration: 5.minutes)
         
     | 
| 
       70 
70 
     | 
    
         
             
            ```
         
     | 
| 
       71 
71 
     | 
    
         | 
| 
       72 
72 
     | 
    
         
             
            ## Advanced Usage
         
     | 
| 
         @@ -112,7 +112,7 @@ customer = Customer.new(customer_id: 'cust_123') 
     | 
|
| 
       112 
112 
     | 
    
         
             
            customer.save
         
     | 
| 
       113 
113 
     | 
    
         | 
| 
       114 
114 
     | 
    
         
             
            # This will set TTL on the main object AND all related fields
         
     | 
| 
       115 
     | 
    
         
            -
            customer.update_expiration( 
     | 
| 
      
 115 
     | 
    
         
            +
            customer.update_expiration(expiration: 12.hours)
         
     | 
| 
       116 
116 
     | 
    
         
             
            # Sets expiration on:
         
     | 
| 
       117 
117 
     | 
    
         
             
            # - customer:cust_123 (main hash)
         
     | 
| 
       118 
118 
     | 
    
         
             
            # - customer:cust_123:recent_orders (list)
         
     | 
| 
         @@ -137,9 +137,9 @@ class AnalyticsEvent < Familia::Horreum 
     | 
|
| 
       137 
137 
     | 
    
         
             
                save
         
     | 
| 
       138 
138 
     | 
    
         | 
| 
       139 
139 
     | 
    
         
             
                if should_expire?
         
     | 
| 
       140 
     | 
    
         
            -
                  update_expiration( 
     | 
| 
      
 140 
     | 
    
         
            +
                  update_expiration(expiration: 1.hour)
         
     | 
| 
       141 
141 
     | 
    
         
             
                else
         
     | 
| 
       142 
     | 
    
         
            -
                  update_expiration( 
     | 
| 
      
 142 
     | 
    
         
            +
                  update_expiration(expiration: 30.days)
         
     | 
| 
       143 
143 
     | 
    
         
             
                end
         
     | 
| 
       144 
144 
     | 
    
         
             
              end
         
     | 
| 
       145 
145 
     | 
    
         
             
            end
         
     | 
| 
         @@ -200,7 +200,7 @@ class SessionCleanupJob 
     | 
|
| 
       200 
200 
     | 
    
         
             
                # Extend expiration for active sessions
         
     | 
| 
       201 
201 
     | 
    
         
             
                UserSession.all.each do |session|
         
     | 
| 
       202 
202 
     | 
    
         
             
                  if session.recently_active?
         
     | 
| 
       203 
     | 
    
         
            -
                    session.update_expiration( 
     | 
| 
      
 203 
     | 
    
         
            +
                    session.update_expiration(expiration: 30.minutes)
         
     | 
| 
       204 
204 
     | 
    
         
             
                  end
         
     | 
| 
       205 
205 
     | 
    
         
             
                end
         
     | 
| 
       206 
206 
     | 
    
         
             
              end
         
     | 
| 
         @@ -225,7 +225,7 @@ class SessionExpirationMiddleware 
     | 
|
| 
       225 
225 
     | 
    
         
             
                  session = UserSession.find(session_token)
         
     | 
| 
       226 
226 
     | 
    
         | 
| 
       227 
227 
     | 
    
         
             
                  # Extend session TTL on each request
         
     | 
| 
       228 
     | 
    
         
            -
                  session&.update_expiration( 
     | 
| 
      
 228 
     | 
    
         
            +
                  session&.update_expiration(expiration: 30.minutes)
         
     | 
| 
       229 
229 
     | 
    
         
             
                end
         
     | 
| 
       230 
230 
     | 
    
         | 
| 
       231 
231 
     | 
    
         
             
                @app.call(env)
         
     | 
| 
         @@ -268,14 +268,14 @@ end 
     | 
|
| 
       268 
268 
     | 
    
         
             
            class SessionManager
         
     | 
| 
       269 
269 
     | 
    
         
             
              def self.extend_all_sessions(new_ttl)
         
     | 
| 
       270 
270 
     | 
    
         
             
                UserSession.all.each do |session|
         
     | 
| 
       271 
     | 
    
         
            -
                  session.update_expiration( 
     | 
| 
      
 271 
     | 
    
         
            +
                  session.update_expiration(expiration: new_ttl)
         
     | 
| 
       272 
272 
     | 
    
         
             
                end
         
     | 
| 
       273 
273 
     | 
    
         
             
              end
         
     | 
| 
       274 
274 
     | 
    
         | 
| 
       275 
275 
     | 
    
         
             
              def self.expire_inactive_sessions
         
     | 
| 
       276 
276 
     | 
    
         
             
                UserSession.all.select(&:inactive?).each do |session|
         
     | 
| 
       277 
277 
     | 
    
         
             
                  # Set very short TTL for inactive sessions
         
     | 
| 
       278 
     | 
    
         
            -
                  session.update_expiration( 
     | 
| 
      
 278 
     | 
    
         
            +
                  session.update_expiration(expiration: 5.minutes)
         
     | 
| 
       279 
279 
     | 
    
         
             
                end
         
     | 
| 
       280 
280 
     | 
    
         
             
              end
         
     | 
| 
       281 
281 
     | 
    
         | 
| 
         @@ -306,7 +306,7 @@ class DataRetentionService 
     | 
|
| 
       306 
306 
     | 
    
         
             
                  model_class = data_type.to_s.pascalize.constantize
         
     | 
| 
       307 
307 
     | 
    
         | 
| 
       308 
308 
     | 
    
         
             
                  model_class.all.each do |record|
         
     | 
| 
       309 
     | 
    
         
            -
                    record.update_expiration( 
     | 
| 
      
 309 
     | 
    
         
            +
                    record.update_expiration(expiration: ttl)
         
     | 
| 
       310 
310 
     | 
    
         
             
                  end
         
     | 
| 
       311 
311 
     | 
    
         
             
                end
         
     | 
| 
       312 
312 
     | 
    
         
             
              end
         
     | 
| 
         @@ -323,7 +323,7 @@ DataRetentionService.apply_retention_policies 
     | 
|
| 
       323 
323 
     | 
    
         
             
            ```ruby
         
     | 
| 
       324 
324 
     | 
    
         
             
            # ❌ Inefficient: Multiple round trips
         
     | 
| 
       325 
325 
     | 
    
         
             
            sessions.each do |session|
         
     | 
| 
       326 
     | 
    
         
            -
              session.update_expiration( 
     | 
| 
      
 326 
     | 
    
         
            +
              session.update_expiration(expiration: 1.hour)
         
     | 
| 
       327 
327 
     | 
    
         
             
            end
         
     | 
| 
       328 
328 
     | 
    
         | 
| 
       329 
329 
     | 
    
         
             
            # ✅ Efficient: Batch operations
         
     | 
| 
         @@ -357,7 +357,7 @@ class ResilientSession < Familia::Horreum 
     | 
|
| 
       357 
357 
     | 
    
         
             
                return unless exists?
         
     | 
| 
       358 
358 
     | 
    
         | 
| 
       359 
359 
     | 
    
         
             
                begin
         
     | 
| 
       360 
     | 
    
         
            -
                  update_expiration( 
     | 
| 
      
 360 
     | 
    
         
            +
                  update_expiration(expiration: new_ttl)
         
     | 
| 
       361 
361 
     | 
    
         
             
                rescue => e
         
     | 
| 
       362 
362 
     | 
    
         
             
                  # Log error but don't crash the application
         
     | 
| 
       363 
363 
     | 
    
         
             
                  Familia.logger.warn "Failed to update expiration for #{dbkey}: #{e.message}"
         
     | 
| 
         @@ -377,7 +377,7 @@ Familia.debug = true 
     | 
|
| 
       377 
377 
     | 
    
         | 
| 
       378 
378 
     | 
    
         
             
            session = UserSession.new(session_token: 'debug_session')
         
     | 
| 
       379 
379 
     | 
    
         
             
            session.save
         
     | 
| 
       380 
     | 
    
         
            -
            session.update_expiration( 
     | 
| 
      
 380 
     | 
    
         
            +
            session.update_expiration(expiration: 5.minutes)
         
     | 
| 
       381 
381 
     | 
    
         
             
            # Logs will show:
         
     | 
| 
       382 
382 
     | 
    
         
             
            # [update_expiration] Expires session:debug_session in 300.0 seconds
         
     | 
| 
       383 
383 
     | 
    
         
             
            ```
         
     | 
| 
         @@ -388,11 +388,11 @@ session.update_expiration(default_expiration: 5.minutes) 
     | 
|
| 
       388 
388 
     | 
    
         
             
            ```ruby
         
     | 
| 
       389 
389 
     | 
    
         
             
            session = UserSession.new
         
     | 
| 
       390 
390 
     | 
    
         
             
            # ❌ Won't work - object must be saved first
         
     | 
| 
       391 
     | 
    
         
            -
            session.update_expiration( 
     | 
| 
      
 391 
     | 
    
         
            +
            session.update_expiration(expiration: 1.hour)
         
     | 
| 
       392 
392 
     | 
    
         | 
| 
       393 
393 
     | 
    
         
             
            # ✅ Correct - save first, then expire
         
     | 
| 
       394 
394 
     | 
    
         
             
            session.save
         
     | 
| 
       395 
     | 
    
         
            -
            session.update_expiration( 
     | 
| 
      
 395 
     | 
    
         
            +
            session.update_expiration(expiration: 1.hour)
         
     | 
| 
       396 
396 
     | 
    
         
             
            ```
         
     | 
| 
       397 
397 
     | 
    
         | 
| 
       398 
398 
     | 
    
         
             
            **2. Related Fields Not Expiring**
         
     | 
| 
         @@ -459,7 +459,7 @@ RSpec.describe UserSession do 
     | 
|
| 
       459 
459 
     | 
    
         | 
| 
       460 
460 
     | 
    
         
             
                it "applies TTL to database key" do
         
     | 
| 
       461 
461 
     | 
    
         
             
                  session.save
         
     | 
| 
       462 
     | 
    
         
            -
                  session.update_expiration( 
     | 
| 
      
 462 
     | 
    
         
            +
                  session.update_expiration(expiration: 10.minutes)
         
     | 
| 
       463 
463 
     | 
    
         | 
| 
       464 
464 
     | 
    
         
             
                  ttl = session.ttl
         
     | 
| 
       465 
465 
     | 
    
         
             
                  expect(ttl).to be > 500  # Should be close to 600 seconds
         
     | 
| 
         @@ -470,7 +470,7 @@ RSpec.describe UserSession do 
     | 
|
| 
       470 
470 
     | 
    
         
             
                  session.save
         
     | 
| 
       471 
471 
     | 
    
         
             
                  session.activity_log.push('login')  # Assume activity_log is a list
         
     | 
| 
       472 
472 
     | 
    
         | 
| 
       473 
     | 
    
         
            -
                  session.update_expiration( 
     | 
| 
      
 473 
     | 
    
         
            +
                  session.update_expiration(expiration: 5.minutes)
         
     | 
| 
       474 
474 
     | 
    
         | 
| 
       475 
475 
     | 
    
         
             
                  # Both main object and related fields should have TTL
         
     | 
| 
       476 
476 
     | 
    
         
             
                  expect(session.ttl).to be > 250
         
     | 
| 
         @@ -542,7 +542,7 @@ class TTLHealthCheck 
     | 
|
| 
       542 
542 
     | 
    
         
             
                    expired_count += 1
         
     | 
| 
       543 
543 
     | 
    
         
             
                  elsif ttl < 300  # Less than 5 minutes remaining
         
     | 
| 
       544 
544 
     | 
    
         
             
                    # Extend TTL for active sessions
         
     | 
| 
       545 
     | 
    
         
            -
                    session.update_expiration( 
     | 
| 
      
 545 
     | 
    
         
            +
                    session.update_expiration(expiration: 30.minutes) if session.active?
         
     | 
| 
       546 
546 
     | 
    
         
             
                  end
         
     | 
| 
       547 
547 
     | 
    
         
             
                end
         
     | 
| 
       548 
548 
     | 
    
         | 
| 
         @@ -565,7 +565,7 @@ class RobustSessionManager 
     | 
|
| 
       565 
565 
     | 
    
         
             
                # Check if session exists and hasn't expired
         
     | 
| 
       566 
566 
     | 
    
         
             
                if session&.ttl&.positive?
         
     | 
| 
       567 
567 
     | 
    
         
             
                  # Extend TTL on access
         
     | 
| 
       568 
     | 
    
         
            -
                  session.update_expiration( 
     | 
| 
      
 568 
     | 
    
         
            +
                  session.update_expiration(expiration: 30.minutes)
         
     | 
| 
       569 
569 
     | 
    
         
             
                  session
         
     | 
| 
       570 
570 
     | 
    
         
             
                else
         
     | 
| 
       571 
571 
     | 
    
         
             
                  # Create new session if old one expired
         
     |