Familia v2.0.0-pre Series Technical Reference
Familia is a Ruby ORM for Valkey/Redis providing object mapping, relationships, and advanced features like encryption, connection pooling, and permission systems. This technical reference covers the major classes, methods, and usage patterns introduced in the v2.0.0-pre series.
For conceptual understanding and getting started with Familia, see the Overview Guide. This document provides detailed implementation patterns and advanced configuration options.
Core Architecture
Base Classes
Familia::Horreum - Primary ORM Base Class
The main base class for Valkey/Redis-backed objects, similar to ActiveRecord models.
class User < Familia::Horreum
# Basic field definitions
field :name, :email, :created_at
# Valkey/Redis data types as instance variables
list :sessions # Valkey/Redis list
set :tags # Valkey/Redis set
zset :scores # Valkey/Redis sorted set
hashkey :settings # Valkey/Redis hash
end
Key Methods:
save- Persist object to Valkey/Redissave_if_not_exists- Conditional persistence, returns false if existssave_if_not_exists!- Conditional persistence, raises RecordExistsError if existsload- Load object from Valkey/Redisexists?- Check if object exists in Valkey/Redisdestroy- Remove object from Valkey/Redis
Familia::DataType - Valkey/Redis Data Type Wrapper
Base class for Valkey/Redis data type implementations.
Registered Types:
String- Valkey/Redis stringsList- Valkey/Redis listsSet- Valkey/Redis setsSortedSet- Valkey/Redis sorted setsHashKey- Valkey/Redis hashesCounter- Atomic countersLock- Distributed locks
Feature System
Feature Architecture
Modular system for extending Horreum classes with reusable functionality.
class Customer < Familia::Horreum
feature :expiration # TTL management
feature :safe_dump # API-safe serialization
feature :encrypted_fields # Field encryption
feature :transient_fields # Non-persistent fields
feature :relationships # Object relationships
field :name, :email
encrypted_field :api_key
transient_field :password
end
Built-in Features
1. Expiration Feature
TTL (Time To Live) management with cascading expiration.
class Session < Familia::Horreum
feature :expiration
field :user_id, :token
default_expiration 24.hours
# Cascade expiration to related objects
cascade_expiration_to :user_activity
end
session = Session.new(user_id: 123, token: "abc123")
session.save
session.expire_in(1.hour) # UnsortedSet custom expiration
session.ttl # Check remaining time
2. SafeDump Feature
API-safe serialization excluding sensitive fields.
class User < Familia::Horreum
feature :safe_dump
field :name, :email, :password_hash
safe_dump_field :name, :email # Only these fields in safe_dump
end
user = User.new(name: "Alice", email: "alice@example.com", password_hash: "secret")
user.safe_dump # => {"name" => "Alice", "email" => "alice@example.com"}
3. Encrypted Fields Feature
Transparent field-level encryption with multiple providers.
# Configuration
Familia.configure do |config|
config.encryption_keys = {
v1: ENV['FAMILIA_ENCRYPTION_KEY'],
v2: ENV['FAMILIA_ENCRYPTION_KEY_V2']
}
config.current_key_version = :v2
end
class Vault < Familia::Horreum
feature :encrypted_fields
field :name # Plaintext
encrypted_field :secret_key # Encrypted with XChaCha20-Poly1305
encrypted_field :api_token # Field-specific key derivation
encrypted_field :private_data # Transparent access
end
vault = Vault.new(
name: "Production Secrets",
secret_key: "super-secret-123",
api_token: "sk-1234567890"
)
vault.save
# Transparent access - automatically encrypted/decrypted
vault.secret_key # => "super-secret-123" (decrypted on access)
Encryption Providers:
- XChaCha20-Poly1305 (preferred) - Requires
rbnaclgem - AES-256-GCM (fallback) - Uses OpenSSL, no dependencies
Advanced Security Implementation:
# Multiple encryption providers with fallback
class CriticalData < Familia::Horreum
feature :encrypted_fields
encrypted_field :credit_card
encrypted_field :ssn
encrypted_field :notes
end
# Key versioning and rotation
Familia.configure do |config|
config.encryption_keys = {
v1: ENV['OLD_KEY'], # Legacy key
v2: ENV['CURRENT_KEY'], # Current key
v3: ENV['NEW_KEY'] # New key for rotation
}
config.current_key_version = :v2
config.encryption_personalization = 'MyApp-2024' # Optional (XChaCha20 only)
end
# Operations with encrypted fields
record = CriticalData.new(
credit_card: "4111-1111-1111-1234",
ssn: "123-45-6789",
notes: "Customer notes"
)
record.save
# Verify encryption status
puts "Record #{record.identifier}: #{status}"
# => {credit_card: {encrypted: true, algorithm: "xchacha20poly1305", cleared: false}}
ConcealedString Security Features:
# Automatic protection against accidental exposure
user = CriticalData.load("user123")
# Safe operations
user.credit_card.class # => ConcealedString
user.credit_card.to_s # => "[CONCEALED]"
user.credit_card.inspect # => "[CONCEALED]"
user.credit_card.to_json # => "\"[CONCEALED]\""
# Explicit access when needed
actual_card = user.credit_card.reveal # => "4111-1111-1111-1234"
# Logging safety
Rails.logger.info "Processing card: #{user.credit_card}"
# => "Processing card: [CONCEALED]" (safe for logs)
# JSON serialization safety
user_data = user.to_json
# All encrypted fields show as "[CONCEALED]" in JSON
4. Transient Fields Feature
Non-persistent fields with memory-safe handling.
class LoginForm < Familia::Horreum
feature :transient_fields
field :username # Persistent
transient_field :password # Never stored in Redis
transient_field :csrf_token # Runtime only
end
form = LoginForm.new(username: "alice", password: "secret123")
form.save # Only saves 'username', password is transient
form.password.class # => Familia::Features::TransientFields::RedactedString
form.password.to_s # => "[REDACTED]" (safe for logging)
form.password.reveal # => "secret123" (explicit access)
RedactedString - Security wrapper preventing accidental exposure:
to_sreturns "[REDACTED]"inspectreturns "[REDACTED]"revealmethod for explicit access- Safe for logging and serialization
5. Relationships Feature
Comprehensive object relationship system with automatic management, clean Ruby-idiomatic syntax, and simplified method generation.
class Customer < Familia::Horreum
feature :relationships
identifier_field :custid
field :custid, :name, :email
# Collections for storing related object IDs
set :domains # Simple set
zset :activity # Scored/sorted collection
# Class-level indexed lookups (automatically managed on save/destroy)
class_indexed_by :email, :email_lookup
# Class-level tracking with scoring (automatically managed on save/destroy)
class_participates_in :all_customers, score: :created_at
end
class Domain < Familia::Horreum
feature :relationships
identifier_field :domain_id
field :domain_id, :name, :status
# Bidirectional membership with clean << operator support
participates_in Customer, :domains
# Relationship-scoped indexing (per-customer domain lookups)
indexed_by :name, :domain_index, target: Customer
# Class-level conditional tracking with lambda scoring
class_participates_in :active_domains,
score: -> { status == 'active' ? Familia.now.to_i : 0 }
end
Relationship Operations with Automatic Management:
# Create and save objects (automatic indexing and tracking)
customer = Customer.new(custid: "cust123", name: "Acme Corp", email: "admin@acme.com")
customer.save # Automatically adds to email_lookup and all_customers
domain = Domain.new(domain_id: "dom456", name: "acme.com", status: "active")
domain.save # Automatically adds to active_domains
# Establish relationships using clean Ruby-like << operator
customer.domains << domain # Clean, Ruby-idiomatic collection syntax
# Query relationships
domain.in_customer_domains?(customer.custid) # => true
customer.domains.member?(domain.identifier) # => true
# O(1) indexed lookups (automatic management - no manual calls needed)
found_id = Customer.email_lookup.get("admin@acme.com")
found_customer = Customer.find_by_email("admin@acme.com") # Convenience method
# Relationship-scoped lookups
customer_domain = customer.find_by_name("acme.com") # Find within customer
# Class-level tracking queries
recent_customers = Customer.all_customers.range_by_score(
(Familia.now - 24.hours).to_i, '+inf'
)
active_domains = Domain.active_domains.members
Key Features:
- Automatic Management: Objects automatically added/removed from class-level collections on save/destroy
- Clean Syntax: Collections support Ruby-like
customer.domains << domainsyntax - Simplified Methods: No complex "global" terminology - uses clear
class_prefixes - Performance: O(1) hash lookups and efficient sorted set operations
- Flexibility: Supports class-level and relationship-scoped indexing patterns
6. Object Identifier Feature
Automatic generation of unique object identifiers with configurable strategies.
Default generator is :uuid_v7 (UUID version 7 with embedded timestamp).
class Document < Familia::Horreum
feature :object_identifier # Uses default :uuid_v7
field :title, :content, :created_at
end
class Session < Familia::Horreum
feature :object_identifier, generator: :hex, length: 16
field :user_id, :data, :expires_at
end
class ApiKey < Familia::Horreum
feature :object_identifier, generator: :custom
field :name, :permissions, :created_at
# Custom generator implementation
def self.generate_identifier
"ak_#{SecureRandom.alphanumeric(32)}"
end
end
Generator Types:
:uuid_v7- UUID version 7 with embedded timestamp (default, 36 characters):uuid_v4- Standard UUID v4 format (36 characters):hex- High-entropy hexadecimal strings (256-bit via SecureIdentifier)- Proc/Lambda - Custom generation logic provided as a callable
Technical Implementation:
# Auto-generated on object creation
doc = Document.create(title: "My Document")
doc.objid # => "01234567-89ab-7def-8000-123456789abc" # UUID v7 format
session = Session.create(user_id: "123")
session.objid # => "a1b2c3d4e5f67890"
# Custom identifier with proc
api_key = ApiKey.create(name: "Production API")
api_key.objid # => "ak_Xy9ZaBcD3fG8HjKlMnOpQrStUvWxYz12"
# Feature options per-class isolation
Document.(:object_identifier)
#=> {generator: :uuid_v7}
7. External Identifier Feature
Derives deterministic external identifiers from object identifiers.
class ExternalUser < Familia::Horreum
feature :external_identifier
identifier_field :id
field :id, :name
# External ID is automatically derived from objid
# Format: 'ext_' + base36(truncated_hash(objid))
end
class APIKey < Familia::Horreum
feature :external_identifier, format: 'api-%{id}'
field :name, :permissions
end
External ID Management:
# External ID is deterministically derived from objid
user = ExternalUser.new(name: "John Doe")
user.save
user.objid # => "01234567-89ab-7def-8000-123456789abc"
user.extid # => "ext_abc123def456ghi789" # Deterministic from objid
# Same objid always produces same extid
user2 = ExternalUser.new(objid: user.objid, name: "John Doe")
user2.extid # => "ext_abc123def456ghi789" # Identical
# Custom format with APIKey
key = APIKey.new(name: "Production")
key.extid # => "api-xyz789abc123"
8. Quantization Feature
Time-based data bucketing for analytics and caching.
class DailyMetric < Familia::Horreum
feature :quantization
identifier_field :metric_key
field :metric_key, :bucket_timestamp, :value_count, :sum_value
# Example: Basic time quantization
string :counter, default_expiration: 1.day, quantize: [10.minutes, '%H:%M']
end
Basic Usage:
# Create metric with quantized timestamp
metric = DailyMetric.new(metric_key: "page_views")
metric.counter.increment
# Time-based data grouping occurs automatically
# All data within the same 10-minute window shares the same key
Key Benefits:
- Time Bucketing: Group time-based data into configurable intervals
- Reduced Storage: Aggregate similar data points to optimize memory usage
- Analytics Ready: Perfect for dashboards and time-series data visualization
Advanced Feature System Architecture
Feature Autoloader for Project Organization
Automatically load features from directory structure.
# app/models/customer.rb - Main model file
class Customer < Familia::Horreum
include Familia::Features::Autoloader
# Automatically loads all .rb files from app/models/customer/*.rb
# Core model definition
identifier_field :custid
field :custid, :name, :email, :created_at
end
# app/models/customer/notifications.rb
class Customer < Familia::Horreum
def send_welcome_email
NotificationService.send_template(
email: email,
template: 'customer_welcome',
variables: { name: name, custid: custid }
)
end
def send_invoice_reminder(invoice_id)
NotificationService.send_template(
email: email,
template: 'invoice_reminder',
variables: { invoice_id: invoice_id }
)
end
end
# app/models/customer/analytics.rb
class Customer < Familia::Horreum
# Analytics methods added to Customer
def track_activity(activity_type, = {})
activity_data = {
custid: custid,
activity: activity_type,
timestamp: Familia.now.to_i,
metadata:
}
# Store in customer's activity stream
activities.unshift(activity_data.to_json)
activities.trim(0, 999) # Keep last 1000 activities
end
def recent_activities(limit = 10)
activities.range(0, limit - 1).map { |json| JSON.parse(json) }
end
end
Feature Dependencies
Features can declare dependencies that are automatically resolved.
# External identifier depends on object_identifier
class User < Familia::Horreum
feature :external_identifier # Automatically includes :object_identifier
field :name
end
# Feature dependency resolution
Familia::Base.add_feature ExternalIdentifier, :external_identifier, depends_on: [:object_identifier]
# When external_identifier is included, object_identifier is automatically loaded first
end
Per-Class Feature Registration
Register custom features for specific model classes with ancestry chain lookup.
# Define a custom feature module
module CustomerAnalytics
def track_purchase(amount)
purchases.increment(amount)
end
end
# Register feature only for Customer and its subclasses
Customer.add_feature CustomerAnalytics, :customer_analytics
class Customer < Familia::Horreum
feature :customer_analytics # Available via Customer's registry
end
class PremiumCustomer < Customer
feature :customer_analytics # Inherited via ancestry chain
end
class Session < Familia::Horreum
# feature :customer_analytics # Not available - would raise error
end
Benefits:
- Features can have the same name across different model hierarchies
- Natural inheritance through Ruby's class hierarchy
- Better namespace management for large applications
Per-Class Feature Configuration Isolation
Each class maintains independent feature options.
class PrimaryCache < Familia::Horreum
feature :expiration
feature :object_identifier, generator: :uuid_v7
field :cache_key, :value, :hit_count
default_expiration 24.hours
end
class SecondaryCache < Familia::Horreum
feature :expiration
feature :object_identifier, generator: :hex # Different generator
field :cache_key, :backup_value, :backup_timestamp
default_expiration 7.days
end
# Feature options are completely isolated
PrimaryCache.(:object_identifier)
#=> {generator: :uuid_v7}
SecondaryCache.(:object_identifier)
#=> {generator: :hex}
Runtime Feature Checking
Check which features are enabled on a class.
class SecureModel < Familia::Horreum
feature :expiration
feature :encrypted_fields
feature :safe_dump
field :name, :status
encrypted_field :api_key
safe_dump_field :name
end
# Check enabled features
SecureModel.features_enabled
#=> [:expiration, :encrypted_fields, :safe_dump]
# Check feature options
SecureModel.(:encrypted_fields)
#=> {} # Default options
# Each class tracks its own features
class BasicModel < Familia::Horreum
feature :expiration
field :name
end
BasicModel.features_enabled
#=> [:expiration]
Connection Management
Connection Provider Pattern
Flexible connection pooling with provider-based architecture.
# Basic Valkey/Redis connection
Familia.configure do |config|
config.uri = "redis://localhost:6379/0"
end
# Custom connection provider with pooling
require 'connection_pool'
Familia.connection_provider = lambda do |uri|
# Provider MUST return connection already on correct database
parsed = URI.parse(uri)
pool_key = "#{parsed.host}:#{parsed.port}/#{parsed.db || 0}"
@pools ||= {}
@pools[pool_key] ||= ConnectionPool.new(size: 10, timeout: 5) do
Redis.new(
host: parsed.host,
port: parsed.port,
db: parsed.db || 0
)
end
@pools[pool_key].with { |conn| conn }
end
Multi-Database Support
Configure different logical databases for different models.
class User < Familia::Horreum
logical_database 0 # Use database 0
field :name, :email
end
class Session < Familia::Horreum
logical_database 1 # Use database 1
field :user_id, :token
end
class Cache < Familia::Horreum
logical_database 2 # Use database 2
field :key, :value
end
Testing and Debugging
Database Command Logging
Monitor all Redis commands with DatabaseLogger middleware.
# Enable command logging (middleware registered automatically)
Familia.enable_database_logging = true
# Capture commands in tests
commands = DatabaseLogger.capture_commands do
user = User.create(name: "Test User")
user.save
end
puts commands.first.command # => "SET user:123 {...}"
puts commands.first.μs # => 567 (microseconds)
# Enable sampling for production
DatabaseLogger.sample_rate = 0.01 # Log 1% of commands
# Structured logging format
DatabaseLogger.structured_logging = true
# => "Redis command cmd=SET args=[key, value] duration_ms=0.42 db=0"
Debug Mode
Enable comprehensive debugging output.
# Via environment variable
ENV['FAMILIA_DEBUG'] = '1'
ENV['FAMILIA_TRACE'] = '1'
# Via configuration
Familia.configure do |config|
config.debug = true
end
# Check database contents
Familia.dbclient.keys('user:*')
# Trace specific operations
Familia.trace :LOAD, redis_client, "user:123", "from cache"
Connection Chain Debugging
Understand connection resolution order.
# Connection resolution order:
# 1. Instance @dbclient if set
# 2. Class logical_database if configured
# 3. Connection provider if set
# 4. Global Familia connection
class DebugModel < Familia::Horreum
logical_database 3
field :name
end
model = DebugModel.new(name: "test")
# Uses database 3 from logical_database
model.dbclient = custom_connection
# Now uses custom_connection instead
Performance Considerations
Encryption Performance
- XChaCha20-Poly1305 is ~2x faster than AES-256-GCM
- Key derivation is NOT cached by default for security
- Use request-level caching for bulk operations:
Familia::Encryption.with_request_cache do
# Bulk operations with cached key derivation
1000.times do |i|
User.create(email: "user#{i}@example.com")
end
end
Connection Pooling
- Use connection_provider for multi-threaded environments
- Pool size should match thread count
- Connections are thread-safe when using provider pattern
Feature Performance Impact
- Encryption: ~10-20% overhead for field access
- Relationships: O(1) for indexed lookups
- Quantization: Minimal overhead, improves storage efficiency
- Safe Dump: Lazy evaluation, only computed when called
Migration Guide
From Familia v1.x to v2.0
- Replace
redis_uriwithuriin configuration - Update feature syntax from mixins to
featuredeclarations - Migrate from
global_prefix toclass_for class-level methods - Update encryption configuration to new provider system
Connection Provider Pattern
- Familia v2.0 uses redis-rb gem internally
- Connection providers must return Redis connections
- Uses RedisClient middleware architecture internally via redis-rb
Summary
Familia v2.0.0-pre series provides a comprehensive ORM for Valkey/Redis with:
- Modular Feature System: Isolated, configurable features per class
- Advanced Security: Field-level encryption with multiple providers
- Flexible Relationships: Automatic management with clean Ruby syntax
- Performance Optimized: Connection pooling, sampling, and caching
- Production Ready: Debug logging, monitoring, and thread safety
For additional documentation and examples, see the Familia GitHub repository.
class ActivityTracker < Familia::Horreum feature :relationships
identifier_field :activity_id field :activity_id, :user_id, :activity_type, :data, :created_at
# Class-level tracking with automatic management class_participates_in :user_activities, score: :created_at class_participates_in :activity_by_type, score: -> { "#activity_type:#created_at".hash } end
Create and save activity (automatic tracking)
activity = ActivityTracker.new( activity_id: 'act123', user_id: 'user456', activity_type: 'login', created_at: Familia.now.to_i ) activity.save # Automatically added to both tracking collections
Query recent activities (last hour)
hour_ago = (Familia.now - 1.hour).to_i recent_activities = ActivityTracker.user_activities.range_by_score( hour_ago, '+inf' )
Get activities by type in time range
login_hash_start = "login:#hour_ago".hash login_hash_end = "login:#Familia.now.to_i".hash login_activities = ActivityTracker.activity_by_type.range_by_score( login_hash_start, login_hash_end )
---
## Data Type Usage Patterns
### Advanced Sorted Set Operations
Leverage Valkey/Redis sorted sets for rankings, time series, and scored data.
```ruby
class Leaderboard < Familia::Horreum
identifier_field :game_id
field :game_id, :name
sorted_set :scores
end
leaderboard = Leaderboard.new(game_id: "game1", name: "Daily Challenge")
# Add player scores
leaderboard.scores.add(1500, "player1")
leaderboard.scores.add(2300, "player2")
leaderboard.scores.add(1800, "player3")
# Get top 10 players (highest scores first)
top_players = leaderboard.scores.revrange(0, 9, withscores: true)
# => [["player2", 2300.0], ["player3", 1800.0], ["player1", 1500.0]]
# Get player rank (0-indexed, lower scores = lower rank)
rank = leaderboard.scores.rank("player1") # => 0
rev_rank = leaderboard.scores.revrank("player1") # => 2 (highest to lowest)
# Get score range
mid_tier = leaderboard.scores.rangebyscore(1000, 2000, withscores: true)
# Increment score atomically
leaderboard.scores.increment("player1", 100) # Add 100 to existing score
List-Based Queues and Feeds
Use Valkey/Redis lists for queues, feeds, and ordered data.
class TaskQueue < Familia::Horreum
identifier_field :queue_name
field :queue_name
list :tasks
end
class ActivityFeed < Familia::Horreum
identifier_field :user_id
field :user_id
list :activities
end
# Task queue operations
queue = TaskQueue.new(queue_name: "email_processing")
# Add tasks to queue (right push)
queue.tasks.push({
type: "send_email",
recipient: "user@example.com",
template: "welcome"
}.to_json)
# Process tasks (left pop - FIFO)
next_task = queue.tasks.pop # Atomic pop from left
task_data = JSON.parse(next_task) if next_task
# Activity feed with size limit
feed = ActivityFeed.new(user_id: "user123")
# Add activity (keep last 100)
feed.activities.unshift("User logged in at #{Familia.now}")
feed.activities.trim(0, 99) # Keep only last 100 items
# Get recent activities
recent = feed.activities.range(0, 9) # Get 10 most recent
Hash-Based Configuration Storage
Store structured configuration and key-value data.
class UserPreferences < Familia::Horreum
identifier_field :user_id
field :user_id
hash :settings
hash :feature_flags
end
prefs = UserPreferences.new(user_id: "user123")
# UnsortedSet individual preferences
prefs.settings["theme"] = "dark"
prefs.settings["notifications"] = "true"
prefs.settings["timezone"] = "UTC-5"
# Batch set multiple values
prefs.feature_flags.update(
"beta_ui" => "true",
"new_dashboard" => "false",
"advanced_features" => "true"
)
# Get preferences
theme = prefs.settings["theme"] # => "dark"
all_settings = prefs.settings.to_h # => Hash of all settings
# Check feature flags
beta_enabled = prefs.feature_flags["beta_ui"] == "true"
Error Handling and Validation
Connection Error Handling
Robust error handling for Valkey/Redis connection issues.
class ResilientService < Familia::Horreum
field :name, :data
def self.with_fallback(&block)
retries = 3
begin
yield
rescue Redis::ConnectionError, Redis::TimeoutError => e
retries -= 1
if retries > 0
sleep(0.1 * (4 - retries)) # Exponential backoff
retry
else
Familia.warn "Database operation failed after retries: #{e.}"
nil # Return nil or handle gracefully
end
end
end
def save_with_fallback
self.class.with_fallback { save }
end
end
Data Validation Patterns
Implement validation in model classes.
class User < Familia::Horreum
field :email, :username, :age
def valid?
errors.clear
validate_email
validate_username
validate_age
errors.empty?
end
def errors
@errors ||= []
end
private
def validate_email
unless email&.include?('@')
errors << "Email must be valid"
end
end
def validate_username
if username.nil? || username.length < 3
errors << "Username must be at least 3 characters"
end
end
def validate_age
unless age.is_a?(Integer) && age > 0
errors << "Age must be a positive integer"
end
end
end
# Usage
user = User.new(email: "invalid", username: "ab", age: -5)
if user.valid?
user.save
else
puts "Validation errors: #{user.errors.join(', ')}"
end
Performance Optimization
Pipelined Bulk Loading
Load multiple objects efficiently with a single pipelined Redis batch.
# Before: N×2 commands (EXISTS + HGETALL per object)
users = ids.map { |id| User.find_by_id(id) }
# For 14 objects: 28 Redis commands
# After: 1 pipelined batch
users = User.load_multi(ids)
# For 14 objects: 1 batch with 14 HGETALL commands (2× faster)
# Load by full dbkeys
users = User.load_multi_by_keys(['user:123:object', 'user:456:object'])
# Filter out nils for missing objects
existing_users = User.load_multi(ids).compact
Optional EXISTS Check Optimization
Skip the EXISTS check for 50% reduction in Redis commands when keys are known to exist.
# Default behavior (2 commands: EXISTS + HGETALL)
user = User.find_by_id(123)
# Optimized (1 command: HGETALL only)
user = User.find_by_id(123, check_exists: false)
When to use check_exists: false:
- Loading from sorted set results (keys guaranteed to exist)
- High-throughput API endpoints
- Bulk operations with known-existing keys
Batch Operations
Minimize Valkey/Redis round trips with batch operations.
# Instead of multiple individual operations
users = []
100.times do |i|
user = User.new(name: "User #{i}", email: "user#{i}@example.com")
users << user
end
# Use transactions for batch saves
User.pipelined do
users.each do |user|
# All operations batched in pipeline
user.save
end
end
Index Rebuilding
Auto-generated rebuild methods for unique and multi indexes with zero downtime.
class User < Familia::Horreum
feature :relationships
unique_index :email, :email_lookup
end
# Rebuild class-level unique index
User.rebuild_email_lookup
# With progress tracking
User.rebuild_email_lookup(batch_size: 100) do |progress|
puts "#{progress[:completed]}/#{progress[:total]}"
end
# Instance-scoped index rebuild
company.rebuild_badge_index
When to use:
- After data migrations or bulk imports
- Recovering from index corruption
- Adding indexes to existing data
Memory Optimization
Efficient memory usage patterns.
class CacheEntry < Familia::Horreum
feature :expiration
field :key, :value, :created_at
default_expiration 1.hour
# Use shorter field names to reduce memory
field :k, :v, :c # Instead of key, value, created_at
# Compress large values
def value=(val)
@value = val.length > 1000 ? Zlib.deflate(val) : val
end
def value
val = @value || ""
val.start_with?("\x78\x9c") ? Zlib.inflate(val) : val
rescue Zlib::DataError
@value # Return original if decompression fails
end
end
Connection Pool Sizing
Configure connection pools based on application needs.
# High-throughput application
Familia.connection_provider = lambda do |uri|
ConnectionPool.new(size: 25, timeout: 5) do
Redis.new(url: uri)
end.with { |conn| conn }
end
# Memory-constrained environment
Familia.connection_provider = lambda do |uri|
ConnectionPool.new(size: 5, timeout: 10) do
Redis.new(url: uri)
end.with { |conn| conn }
end
Migration and Upgrading
From v1.x to v2.0
Key changes and migration steps.
# OLD v1.x syntax
class User < Familia
identifier :email
string :name
list :sessions
end
# NEW v2.0 syntax
class User < Familia::Horreum
identifier_field :email # Updated method name
field :name # Generic field method
list :sessions # Data types unchanged
end
# Feature activation (NEW)
class User < Familia::Horreum
feature :expiration # Explicit feature activation
feature :safe_dump
identifier_field :email
field :name
list :sessions
default_expiration 24.hours # Feature-specific methods
safe_dump_field :name # Feature-specific methods
end
Encryption Migration
Migrating existing fields to encrypted storage.
# Step 1: Add feature without changing existing fields
class User < Familia::Horreum
feature :encrypted_fields # Add feature
field :name, :email
field :api_key # Still plaintext during migration
end
# Step 2: Migrate data with dual read/write
class User < Familia::Horreum
feature :encrypted_fields
field :name, :email
encrypted_field :api_key # Now encrypted
# Temporary migration method
def migrate_api_key!
if raw_api_key = dbclient.hget(dbkey, "api_key") # Read old plaintext
self.api_key = raw_api_key # Write as encrypted
dbclient.hdel(dbkey, "api_key") # Remove plaintext
save
end
end
end
# Step 3: Run migration for existing users
User.instances.each(&:migrate_api_key!)
Testing Patterns
Test Helpers and Utilities
Common patterns for testing Familia applications.
# test_helper.rb
require 'familia'
# Use separate Valkey/Redis database for tests
Familia.configure do |config|
config.uri = ENV.fetch('REDIS_TEST_URI', 'redis://localhost:2525/3')
end
module TestHelpers
def setup_redis
# Clear test database
Familia.dbclient.flushdb
end
def teardown_redis
Familia.dbclient.flushdb
end
def create_test_user(**attrs)
User.new({
email: "test@example.com",
name: "Test User",
created_at: Familia.now.to_i
}.merge(attrs))
end
end
# In test files
class UserTest < Minitest::Test
include TestHelpers
def setup
setup_redis
end
def teardown
teardown_redis
end
def test_user_creation_with_automatic_indexing
user = create_test_user(name: "Alice")
user.save # Automatically adds to class-level indexes
assert user.exists?
assert_equal "Alice", user.name
# Test automatic indexing (if using unique_index)
if User.respond_to?(:find_by_email)
found_user = User.find_by_email(user.email)
assert_equal user.identifier, found_user.identifier
end
end
def test_relationships_with_clean_syntax
user = create_test_user
user.save # Automatic class-level tracking
domain = Domain.new(domain_id: "test_domain", name: "test.com")
domain.save # Automatic class-level tracking
# Test clean relationship syntax
user.domains << domain # Ruby-like collection syntax
assert domain.in_user_domains?(user.identifier)
assert user.domains.member?(domain.identifier)
end
def test_encrypted_fields_concealment
setup_encryption_keys
vault = SecureVault.new(
name: "Test Vault",
secret_key: "super-secret-123",
api_token: "sk-1234567890"
)
vault.save
# Test ConcealedString behavior
assert_instance_of Familia::Features::EncryptedFields::ConcealedString, vault.secret_key
assert_equal "[CONCEALED]", vault.secret_key.to_s
assert_equal "super-secret-123", vault.secret_key.reveal
# Test JSON safety
json_data = vault.to_json
refute_includes json_data, "super-secret-123"
assert_includes json_data, "[CONCEALED]"
end
def test_transient_fields_non_persistence
form = LoginForm.new(
username: "testuser",
password: "secret123",
csrf_token: "abc123"
)
form.save
# Reload and verify transient fields not persisted
reloaded = LoginForm.load(form.identifier)
assert_equal "testuser", reloaded.username
assert_nil reloaded.password
assert_nil reloaded.csrf_token
end
def test_object_identifier_generation
# Test UUID generation
doc = UuidDocument.create(title: "Test Doc")
assert_match(/\A[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\z/i, doc.objid)
# Test hex generation
session = HexSession.create(user_id: "123")
assert_match(/\A[0-9a-f]+\z/i, session.objid)
assert_equal 16, session.objid.length
end
def test_quantization_time_bucketing
= Time.parse("2024-12-15 14:37:23")
bucket = MetricsBucket.()
assert_equal "20241215_1430", bucket # 10-minute bucket
# Test multiple timestamps in same bucket
= Time.parse("2024-12-15 14:39:45")
bucket2 = MetricsBucket.()
assert_equal bucket, bucket2
end
def test_external_identifier_mapping
user = ExternalUser.new(name: "External User")
user.save
# Test external ID is derived from objid
assert_not_nil user.objid
assert_not_nil user.extid
assert user.extid.start_with?("ext_")
# Test deterministic generation
user2 = ExternalUser.new(objid: user.objid, name: "External User")
assert_equal user.extid, user2.extid
end
private
def setup_encryption_keys
test_keys = {
v1: Base64.strict_encode64('a' * 32),
v2: Base64.strict_encode64('b' * 32)
}
Familia.configure do |config|
config.encryption_keys = test_keys
config.current_key_version = :v1
config.encryption_personalization = 'TestApp-Test'
end
end
end
Resources and References
Key Configuration
Essential configuration options for Familia v2.0.0-pre.
Familia.configure do |config|
# Basic Valkey/Redis connection
config.uri = ENV['REDIS_URL'] || 'redis://localhost:6379/0'
# Connection provider for pooling (optional)
config.connection_provider = MyConnectionProvider
# Encryption configuration (for encrypted_fields feature)
config.encryption_keys = {
v1: ENV['FAMILIA_ENCRYPTION_KEY_V1'],
v2: ENV['FAMILIA_ENCRYPTION_KEY_V2']
}
config.current_key_version = :v2
# Debugging and logging
config.debug = ENV['FAMILIA_DEBUG'] == 'true'
config.enable_database_logging = ENV['FAMILIA_LOG_REDIS'] == 'true'
end
Documentation Links
- Familia Repository
- Wiki Home
- Feature System Guide
- Relationships Guide
- Encrypted Fields Overview
- Connection Pooling Guide
Version Information
- Current Version: v2.0.0
- Ruby Compatibility: 3.2+
- Redis Compatibility: 6.0+ (Valkey compatible)
This technical reference covers the major components and usage patterns available in Familia v2.0. For complete API documentation, see the generated YARD docs and wiki guides.