redis-store

RubyRailsCache LibraryRedisSessionI18n

GitHub Overview

redis-store/redis-store

Namespaced Rack::Session, Rack::Cache, I18n and cache Redis stores for Ruby web frameworks

Stars1,499
Watchers24
Forks341
Created:April 11, 2009
Language:Ruby
License:MIT License

Topics

libraryredisruby

Star History

redis-store/redis-store Star History
Data as of: 10/22/2025, 10:04 AM

Cache Library

redis-store

Overview

redis-store is a library family that provides comprehensive Redis stores (Cache, I18n, Session, HTTP Cache) for modern Ruby frameworks like Ruby on Rails, Sinatra, and Rack, though Rails 5.2+ built-in Redis cache store is now recommended for caching purposes.

Details

redis-store is a library family that provides a full set of Redis stores (Cache, I18n, Session, HTTP Cache) for modern Ruby frameworks like Ruby on Rails, Sinatra, Rack, Rack::Cache, and I18n. It supports object marshalling, timeouts, single or multiple node support, and namespace functionality. The redis-store family includes redis-rails for Rails integration, redis-actionpack for session management, and redis-rack-cache for HTTP cache storage. However, Rails 5.2.0 introduced a built-in Redis cache store, making this gem unnecessary if you only need to store fragment cache in Redis. Rails' built-in RedisCacheStore supports multiple Redis clients including vanilla Redis, hiredis, and Redis::Distributed, offering advanced features like fault tolerance, Redis-based sharding, local cache, batch operations (read_multi/write_multi), and compression. For new Rails applications (5.2+), it's recommended to use the built-in Rails Redis cache store instead of redis-store gems for caching purposes. However, maintenance on the redis-activesupport gem continues for security and compatibility issues, but new features are no longer being accepted.

Pros and Cons

Pros

  • Comprehensive Stores: Integrated support for cache, session, I18n, and HTTP cache
  • Framework Integration: Deep integration with Rails, Sinatra, and Rack
  • Namespace Support: Namespace functionality to prevent key collisions
  • Object Marshalling: Automatic serialization of Ruby objects
  • Multi-Node Support: Support for both single and multiple Redis nodes
  • Timeout Configuration: Flexible timeout settings

Cons

  • Deprecated for Rails 5.2+: Built-in Redis cache store is now standard
  • Reduced Maintenance: New feature development has stopped
  • Complexity: Excessive features for simple caching needs
  • Dependencies: Structure dependent on multiple gems
  • Performance: Overhead compared to built-in stores

Key Links

Code Examples

Rails Configuration (Legacy - Using Redis-store)

# Gemfile
gem 'redis-rails'

# config/environments/production.rb
Rails.application.configure do
  # Cache configuration using Redis-store
  config.cache_store = :redis_store, {
    host: 'localhost',
    port: 6379,
    db: 0,
    namespace: 'my_app_cache',
    expires_in: 90.minutes
  }
  
  # Session store configuration
  config.session_store :redis_store, {
    servers: ["redis://localhost:6379/1"],
    expire_after: 90.minutes,
    key: '_my_app_session',
    threadsafe: false
  }
end

# config/initializers/redis_store.rb
Rails.application.config.session_store :redis_store, {
  servers: %w[redis://localhost:6379/1],
  expire_after: 90.minutes,
  key: '_my_app_session'
}

Rails 5.2+ Recommended Configuration (Built-in Redis Cache Store)

# Gemfile
gem 'redis', '~> 5.0'

# config/environments/production.rb
Rails.application.configure do
  # Basic configuration
  config.cache_store = :redis_cache_store, { url: "redis://localhost:6379/0" }
  
  # Advanced configuration
  config.cache_store = :redis_cache_store, {
    url: "redis://localhost:6379/0",
    namespace: "my_app_cache",
    compress: true,
    compress_threshold: 1.kilobyte,
    pool_size: 20,
    pool_timeout: 5,
    reconnect_attempts: 2,
    connect_timeout: 30,
    read_timeout: 0.2,
    write_timeout: 0.2,
    error_handler: -> (method:, returning:, exception:) {
      Rails.logger.warn("Redis cache error: #{exception.message}")
    }
  }
end

# Redis configuration with password
config.cache_store = :redis_cache_store, {
  url: 'redis://localhost:6379/0',
  password: 'your_redis_password'
}

Redis-store Session Management

# config/initializers/session_store.rb
Rails.application.config.session_store :redis_store, {
  servers: [
    {
      host: "localhost",
      port: 6379,
      db: 1,
      password: "mysecret"
    },
    {
      host: "localhost",
      port: 6380,
      db: 1,
      password: "mysecret"
    }
  ],
  expire_after: 90.minutes,
  namespace: "session",
  key: "_my_app_session"
}

# Session usage example
class ApplicationController < ActionController::Base
  def store_user_preference
    session[:theme] = params[:theme]
    session[:language] = params[:language]
    redirect_to root_path, notice: 'Settings saved'
  end
  
  def current_theme
    session[:theme] || 'default'
  end
end

Usage in Rack Applications

# config.ru
require 'rack'
require 'redis-store'
require 'redis-rack-cache'

# Cache middleware configuration
use Rack::Cache,
  metastore: "redis://localhost:6379/0",
  entitystore: "redis://localhost:6379/1"

# Session middleware configuration
use Rack::Session::Redis,
  redis_server: "redis://localhost:6379/2",
  expire_after: 3600

# Application
app = lambda do |env|
  request = Rack::Request.new(env)
  
  # Using sessions
  session = request.session
  session[:visit_count] = (session[:visit_count] || 0) + 1
  
  [200, {}, ["Visit count: #{session[:visit_count]}"]]
end

run app

Using as I18n Backend

# config/initializers/i18n_redis.rb
require 'redis-store'

# Configure Redis as I18n backend
I18n.backend = I18n::Backend::Chain.new(
  I18n::Backend::Redis.new("redis://localhost:6379/3"),
  I18n.backend
)

# Dynamic addition of custom translations
class TranslationManager
  def self.add_translation(locale, key, value)
    I18n.backend.backends.first.store_translations(locale, { key => value })
  end
  
  def self.update_translations(translations_hash)
    translations_hash.each do |locale, translations|
      I18n.backend.backends.first.store_translations(locale, translations)
    end
  end
end

# Usage example
TranslationManager.add_translation(:ja, 'custom.greeting', 'こんにちは!')
TranslationManager.add_translation(:en, 'custom.greeting', 'Hello!')

puts I18n.t('custom.greeting', locale: :ja)  # => "こんにちは!"

Usage in Sinatra Applications

# sinatra_app.rb
require 'sinatra'
require 'redis-store'

# Session configuration
use Rack::Session::Redis, 
    redis_server: "redis://localhost:6379/0",
    expire_after: 3600

# Cache configuration
configure do
  set :cache, Redis::Store.new("redis://localhost:6379/1")
end

# Route definitions
get '/cached_data/:id' do
  cache_key = "data:#{params[:id]}"
  
  # Try to get from cache
  cached_data = settings.cache.get(cache_key)
  return cached_data if cached_data
  
  # Fetch from database (expensive operation)
  data = fetch_data_from_database(params[:id])
  
  # Save to cache (1 hour)
  settings.cache.setex(cache_key, 3600, data)
  
  data
end

get '/session_demo' do
  session[:counter] = (session[:counter] || 0) + 1
  "Session counter: #{session[:counter]}"
end

def fetch_data_from_database(id)
  # Simulate heavy database processing
  sleep(2)
  "Data for ID: #{id} - Generated at #{Time.now}"
end

Namespace and Multi-Tenant Support

class MultiTenantCache
  def initialize(tenant_id)
    @tenant_id = tenant_id
    @redis = Redis::Store.new(
      url: "redis://localhost:6379/0",
      namespace: "tenant:#{tenant_id}"
    )
  end
  
  def set(key, value, expiration = 3600)
    @redis.setex(key, expiration, serialize(value))
  end
  
  def get(key)
    data = @redis.get(key)
    data ? deserialize(data) : nil
  end
  
  def delete(key)
    @redis.del(key)
  end
  
  def exists?(key)
    @redis.exists(key)
  end
  
  def flush_tenant_cache
    pattern = "tenant:#{@tenant_id}:*"
    keys = @redis.keys(pattern)
    @redis.del(*keys) if keys.any?
  end
  
  private
  
  def serialize(value)
    Marshal.dump(value)
  end
  
  def deserialize(data)
    Marshal.load(data)
  end
end

# Usage example
tenant1_cache = MultiTenantCache.new('tenant_1')
tenant2_cache = MultiTenantCache.new('tenant_2')

tenant1_cache.set('user_data', { name: 'Alice', email: '[email protected]' })
tenant2_cache.set('user_data', { name: 'Bob', email: '[email protected]' })

puts tenant1_cache.get('user_data')  # => {:name=>"Alice", :email=>"alice@tenant1.com"}
puts tenant2_cache.get('user_data')  # => {:name=>"Bob", :email=>"bob@tenant2.com"}

Custom Serializer

class JsonRedisStore < Redis::Store
  def set(key, value, options = {})
    serialized_value = JSON.dump(value)
    if options[:expires_in]
      setex(key, options[:expires_in], serialized_value)
    else
      super(key, serialized_value)
    end
  end
  
  def get(key)
    value = super(key)
    value ? JSON.parse(value) : nil
  end
  
  def mget(*keys)
    values = super(*keys)
    values.map { |v| v ? JSON.parse(v) : nil }
  end
end

# Usage example
redis_store = JsonRedisStore.new("redis://localhost:6379/0")

# Store and retrieve hash
user_data = {
  id: 123,
  name: "Alice",
  preferences: {
    theme: "dark",
    language: "ja"
  },
  created_at: Time.now.iso8601
}

redis_store.set("user:123", user_data, expires_in: 3600)
retrieved_data = redis_store.get("user:123")

puts retrieved_data['name']  # => "Alice"
puts retrieved_data['preferences']['theme']  # => "dark"

Performance Monitoring

class MonitoredRedisStore
  def initialize(redis_config)
    @redis = Redis::Store.new(redis_config)
    @stats = {
      hits: 0,
      misses: 0,
      writes: 0,
      deletes: 0
    }
  end
  
  def get(key)
    value = @redis.get(key)
    if value
      @stats[:hits] += 1
    else
      @stats[:misses] += 1
    end
    value
  end
  
  def set(key, value, expiration = nil)
    @stats[:writes] += 1
    if expiration
      @redis.setex(key, expiration, value)
    else
      @redis.set(key, value)
    end
  end
  
  def delete(key)
    @stats[:deletes] += 1
    @redis.del(key)
  end
  
  def hit_rate
    total = @stats[:hits] + @stats[:misses]
    return 0 if total.zero?
    (@stats[:hits].to_f / total * 100).round(2)
  end
  
  def stats
    @stats.merge(hit_rate: "#{hit_rate}%")
  end
  
  def reset_stats
    @stats = @stats.keys.zip([0] * @stats.size).to_h
  end
end

# Usage example
monitored_cache = MonitoredRedisStore.new("redis://localhost:6379/0")

# Cache operations
monitored_cache.set("key1", "value1")
monitored_cache.get("key1")  # hit
monitored_cache.get("key2")  # miss
monitored_cache.get("key1")  # hit

puts monitored_cache.stats
# => {:hits=>2, :misses=>1, :writes=>1, :deletes=>0, :hit_rate=>"66.67%"}