Organize and store Ruby objects in Valkey/Redis using native database types (an ORM of sorts).
Familia provides object-oriented access to Valkey/Redis using their database types. Unlike traditional ORMs that map objects to relational tables, Familia maps Ruby objects directly to Valkey's native data structures (strings, lists, sets, sorted sets, hashes) as instance variables.
Caution
Familia 2 is in pre-release and not ready for production use. (October 2025)
Traditional ORMs convert your objects to SQL tables. A product with categories becomes two tables with a join table. Checking if a tag exists requires a query with joins.
Familia stores your objects using Valkey/Redis data structures directly. A product with categories uses an actual Valkey/Redis list. Checking if a tag exists is a native O(1) Valkey/Redis operation.
# Traditional ORM - everything becomes SQL tables
class Product < ActiveRecord::Base
has_and_belongs_to_many :tags # Creates products_tags junction table
end
product.tags.include?(tag) # SELECT * FROM products_tags WHERE ...
# Familia - uses Valkey/Redis data types directly
class Product < Familia::Horreum
set :tags # Actual Valkey/Redis set
end
product.tags.include?("electronics") # Valkey/Redis SISMEMBER - O(1) operation
When you define a Familia model, each data type declaration creates the corresponding Valkey/Redis structure:
class Product < Familia::Horreum
identifier_field :sku
field :name, :price # Stored in Valkey/Redis hash
list :categories # Actual Valkey/Redis list
set :tags # Actual Valkey/Redis set
zset :ratings # Actual Valkey/Redis sorted set
counter :views # Valkey/Redis string with atomic increment
end
# These are Valkey/Redis native operations, not ORM abstractions
product.categories.push("electronics") # LPUSH
product.tags.add("popular") # SADD
product.ratings.add(4.5, "user123") # ZADD with score
product.views.increment # INCR (atomic)
The performance characteristics you rely on in Valkey/Redis remain unchanged. Set membership is O(1). Sorted sets maintain order automatically. Counters increment atomically without read-modify-write cycles.
# Add to Gemfile
gem 'familia', '>= 2.0'
# Or install directly
gem install familia
# config/initializers/familia.rb (Rails)
# or at the top of your script
require 'familia'
# Basic configuration
Familia.uri = 'redis://localhost:6379/0'
# Or with authentication
Familia.uri = 'redis://user:password@localhost:6379/0'
class User < Familia::Horreum
identifier_field :email
field :email
field :name
field :created_at
end
# Create and save
user = User.create(email: 'alice@example.com', name: 'Alice', created_at: Time.now.to_i)
# Find by identifier
user = User.load('alice@example.com')
# Update and save
user.name = 'Alice Windows'
user.save
# Fast update (immediate persistence)
user.name!('Alice Smith') # Sets and saves immediately
# Check existence
User.exists?('alice@example.com') #=> true
# Delete
user.destroy
# Conditional save
user.save_if_not_exists # Only saves if object doesn't exist yet
Familia automatically generates methods for fields and data types:
class User < Familia::Horreum
field :name # → name, name=, name!
set :tags # → tags, tags=, tags?
list :history # → history, history=, history?
end
# Field methods
user.name # Get field value
user.name = 'Alice' # Set field value
user.name!('Alice') # Set and save immediately
# Data type methods
user.tags # Get Set instance
user.tags = new_set # Replace Set instance
user.tags? # Check if it's a Set type
- Ruby: 3.4+ (3.4+ recommended)
- Valkey/Redis: 6.0+
- Gems:
redis
(automatically installed)
Familia provides direct mappings to Valkey/Redis native data structures:
class BlogPost < Familia::Horreum
identifier_field :slug
# Basic fields
field :slug, :title, :content, :published_at
# Valkey/Redis data types as instance variables
string :view_count, default: '0' # Atomic counters
list :comments # Ordered, allows duplicates
set :tags # Unique values
zset :popularity_scores # Scored/ranked data
hashkey :metadata # Key-value pairs
# Advanced field types
counter :likes # Specialized atomic counter
end
post = BlogPost.create(slug: "hello-world", title: "Hello World")
# Work with Valkey/Redis data types naturally
post.view_count.increment # INCR view_count
post.comments.push("Great post!") # LPUSH comments
post.tags.add("ruby", "programming") # SADD tags
post.popularity_scores.add(4.5, "user123") # ZADD popularity_scores
post.metadata["author"] = "Alice" # HSET metadata
post.likes.increment(5) # INCRBY likes 5
Enable advanced functionality with Familia's modular feature system:
class User < Familia::Horreum
# Enable features as needed
feature :expiration # TTL management
feature :safe_dump # API-safe serialization
feature :encrypted_fields # Field-level encryption
feature :relationships # Object relationships
feature :object_identifier # Auto-generated IDs
feature :quantization # Time-based data bucketing
identifier_field :email
field :email, :name, :created_at
# Feature-specific functionality
encrypted_field :api_key # Automatically encrypted
safe_dump_field :email # Include in safe_dump
safe_dump_field :name # Include in safe_dump
default_expiration 30.days # Auto-expire inactive users
end
user = User.create(email: "alice@example.com", api_key: "secret123")
user.api_key.class # => ConcealedString
user.api_key.to_s # => "[CONCEALED]" (safe for logs)
user.safe_dump # => {email: "...", name: "..."}
# Primary key lookup
user = User.load("alice@example.com")
# Existence checks
User.exists?("alice@example.com") # => true/false
# Bulk operations
users = User.multiget("alice@example.com", "bob@example.com")
# With relationships feature - indexed lookups
class User < Familia::Horreum
feature :relationships
field :email, :username
class_indexed_by :username, :username_lookup
end
# O(1) indexed finding
user = User.find_by_username("alice_doe") # Fast hash lookup
flower = Flower.create(name: "Red Rose", token: "prose")
flower.owners.push("Alice", "Bob")
flower.tags.add("romantic")
flower.metrics.increment("views", 1)
flower.props[:color] = "red"
flower.save
rose = Flower.load("prose")
rose.name = "Pink Rose"
rose.save
user = User.create(username: "prosedog", first_name: "Rose", last_name: "Dog")
user.safe_dump
# => {id: "user:prosedog", username: "prosedog", full_name: "Rose Dog"}
metric = DailyMetric.new
metric.counter.increment # Increments the counter for the current hour
Flower.multiget("prose", "tulip", "daisy")
user.transaction do |conn|
conn.set("user:#{user.id}:status", "active")
conn.zadd("active_users", Familia.now.to_i, user.id)
end
Time-based Expiration:
class Session < Familia::Horreum
feature :expiration
default_expiration 24.hours
field :user_id, :token
end
session = Session.create(user_id: "123", token: "abc123")
session.ttl # Check remaining time
session.expire_in(1.hour) # Custom expiration
Encrypted Fields:
class SecureData < Familia::Horreum
feature :encrypted_fields
field :name
encrypted_field :credit_card, :ssn
end
data = SecureData.create(name: "Alice", credit_card: "4111-1111-1111-1234")
data.credit_card.reveal # => "4111-1111-1111-1234"
data.credit_card.to_s # => "[CONCEALED]"
# config/initializers/familia.rb (Rails)
require 'familia'
# Basic configuration
Familia.uri = 'redis://localhost:6379/0'
# Production configuration
Familia.configure do |config|
config.redis_uri = ENV['REDIS_URL']
config.debug = ENV['FAMILIA_DEBUG'] == 'true'
end
require 'connection_pool'
Familia.connection_provider = lambda do |uri|
ConnectionPool.new(size: 10, timeout: 5) do
Redis.new(url: uri)
end.with { |conn| yield conn if block_given?; conn }
end
Familia.configure do |config|
config.encryption_keys = {
v1: ENV['FAMILIA_ENCRYPTION_KEY_V1'],
v2: ENV['FAMILIA_ENCRYPTION_KEY_V2']
}
config.current_key_version = :v2
end
For large applications, you can organize model complexity using custom features and the Feature Autoloading System:
For large applications, organize features into modular files using the autoloader:
# app/models/customer.rb - Main model file
class Customer < Familia::Horreum
module Features
include Familia::Features::Autoloader
# Automatically loads all .rb files from app/models/customer/features/
end
identifier_field :custid
field :custid, :name, :email
feature :safe_dump # Feature configuration loaded automatically
end
# app/models/customer/features/notifications.rb - Automatically loaded
module Customer::Features::Notifications
def send_welcome_email
NotificationService.send_template(
email: email,
template: 'customer_welcome',
variables: { name: name, custid: custid }
)
end
end
# app/models/customer/features/safe_dump_extensions.rb - Feature-specific config
module Customer::Features::SafeDumpExtensions
def self.included(base)
base.safe_dump_field :custid
base.safe_dump_field :name
base.safe_dump_field :email
end
end
This approach keeps complex models organized while maintaining clean, declarative style.
This version of Familia was developed with assistance from AI tools. The following tools provided significant help with architecture design, code generation, and documentation:
- Google Gemini - Refactoring, code generation, and documentation.
- Claude Sonnet 4, Opus 4.1 - Architecture design, code generation, and documentation
- Claude Desktop & Claude Code (Max plan) - Interactive development sessions and debugging
- GitHub Copilot - Code completion and refactoring assistance
- Qodo Merge Pro - Code review and quality improvements
I remain responsible for all design decisions and the final code. I believe in being transparent about development tools, especially as AI becomes more integrated into our workflows as developers.
For comprehensive guides and detailed technical information:
- Overview Guide - Conceptual understanding and getting started
- Technical Reference - Implementation details and advanced patterns
- Migration Guides - Upgrading from previous versions
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Pull requests are automatically reviewed by Qodo Merge with compliance checks for:
- Error Handling - External API calls and database operations must have proper error handling
- Test Coverage - New features must include tests using the Tryouts framework
- Changelog Fragments - User-facing changes should include a changelog entry
- Documentation - API changes must update documentation
- Backward Compatibility - Breaking changes must be documented
- Thread Safety - Shared state must be properly synchronized
- Database Key Naming - Keys must follow Familia conventions
See docs/qodo-merge-compliance.md for details.
This project is licensed under the MIT License - see the LICENSE file for details.