Migrating Guide: v2.0.0-pre19

This version introduces significant improvements to Familia's database operations, making them more atomic, reliable, and consistent with Rails conventions. The changes include enhanced error handling, optimistic locking support, and breaking API changes.

Breaking Changes

Management.create → create!

What Changed:

The create class method has been renamed to create! to follow Rails conventions and indicate that it raises exceptions on failure.

Before:

user = User.create(email: "test@example.com")

After:

user = User.create!(email: "test@example.com")

Why This Matters:

  • Follows Rails naming conventions where ! methods raise exceptions
  • Makes it clear that CreationError will be raised if object already exists
  • Prevents silent failures in object creation

save_if_not_exists → save_if_not_exists!

What Changed:

The save_if_not_exists method has been renamed to save_if_not_exists! and now includes optimistic locking with automatic retry logic.

Before:

success = user.save_if_not_exists

After:

success = user.save_if_not_exists!

Behavior Changes:

  • Now uses Redis WATCH/MULTI/EXEC for optimistic locking
  • Automatically retries up to 3 times on OptimisticLockError
  • Raises RecordExistsError if object already exists
  • More atomic and thread-safe

Enhanced Error Handling

New Error Hierarchy

What's New:

A structured error hierarchy provides better error categorization:

Familia::Problem                    # Base class
├── Familia::PersistenceError       # Redis/database errors
   ├── Familia::NonUniqueKey
   ├── Familia::OptimisticLockError
   ├── Familia::OperationModeError
   ├── Familia::NoConnectionAvailable
   ├── Familia::NotFound
   └── Familia::NotConnected
└── Familia::HorreumError           # Model-related errors
    ├── Familia::CreationError
    ├── Familia::NoIdentifier
    ├── Familia::FieldTypeError
    ├── Familia::AutoloadError
    ├── Familia::SerializerError
    ├── Familia::UnknownFieldError
    └── Familia::NotDistinguishableError

Migration:

Update exception handling to use specific error classes:

# Before
rescue Familia::Problem => e
  # Handle all errors

# After - More granular handling
rescue Familia::CreationError => e
  # Handle creation failures specifically
rescue Familia::OptimisticLockError => e
  # Handle concurrent modification
rescue Familia::PersistenceError => e
  # Handle database-related errors

New Exception Types

  • CreationError: Raised when object creation fails (replaces generic errors)
  • OptimisticLockError: Raised when WATCH fails due to concurrent modification
  • UnknownFieldError: Raised when referencing non-existent fields

Database Operation Improvements

Atomic Save Operations

What Changed:

The save method now uses a single Redis transaction for complete atomicity:

# All operations now happen atomically:
# 1. Save all fields (HMSET)
# 2. Set expiration (EXPIRE)
# 3. Update indexes
# 4. Add to instances collection

Benefits:

  • Eliminates race conditions during save operations
  • Ensures data consistency across related operations
  • Better performance with fewer round trips

Enhanced Timestamp Precision

What Changed:

Created/updated timestamps now use float values instead of integers for higher precision:

Before:

user.created  # => 1697234567 (integer seconds)

After:

user.created  # => 1697234567.123 (float with milliseconds)

Migration:

No code changes needed. Existing integer timestamps continue to work.

Redis Command Enhancements

New Commands Available

Added support for optimistic locking commands:

  • watch(key) - Watch key for changes
  • unwatch() - Remove all watches
  • discard() - Discard queued commands

Improved Command Logging

Database command logging now includes:

  • Structured format for better readability
  • Pipelined operation tracking
  • Transaction boundary markers
  • Command timing information

Terminology Updates

What Changed:

Standardized on "pipelined" terminology throughout (previously mixed "pipeline"/"pipelined").

Files Affected:

  • Method names now consistently use "pipelined"
  • Documentation updated to match Redis terminology
  • Log messages standardized

Migration:

No code changes needed - this was an internal consistency improvement.

  1. Update method calls:

    # Replace all instances
    Model.create(...)      → Model.create!(...)
    obj.save_if_not_exists → obj.save_if_not_exists!
    
  2. Review error handling:

    # Consider more specific error handling
    rescue Familia::CreationError
    rescue Familia::OptimisticLockError
    
  3. Test concurrent operations:

    • The new optimistic locking provides better concurrency handling
    • Verify your application handles OptimisticLockError appropriately
  4. Review logging:

    • Enhanced database command logging may affect log volume
    • Adjust log levels if needed for production environments