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
CreationErrorwill 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
RecordExistsErrorif 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 modificationUnknownFieldError: 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 changesunwatch()- Remove all watchesdiscard()- 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.
Recommended Actions
Update method calls:
# Replace all instances Model.create(...) → Model.create!(...) obj.save_if_not_exists → obj.save_if_not_exists!Review error handling:
# Consider more specific error handling rescue Familia::CreationError rescue Familia::OptimisticLockErrorTest concurrent operations:
- The new optimistic locking provides better concurrency handling
- Verify your application handles
OptimisticLockErrorappropriately
Review logging:
- Enhanced database command logging may affect log volume
- Adjust log levels if needed for production environments