# coding: utf-8
require 'redis'
require 'redis/namespace'
require 'stockpile'
class Stockpile
# A connection manager for Redis.
class Redis
VERSION = '1.0' # :nodoc:
# Create a new Redis connection manager with the provided options.
#
# == Options
#
# +redis+:: Provides the Redis connection options.
# +namespace+:: Provides the Redis namespace to use, but only if
# redis-namespace is in use (detected with the existence of
# Redis::Namespace).
#
# The namespace can also be provided as a key in the +redis+
# options if it is missing from the main options. If there is
# no namespace key present in either +options+ or
# options[:redis], a namespace will be generated
# from one of the following: $REDIS_NAMESPACE,
# Rails.env (if in Rails), or $RACK_ENV.
# +narrow+:: Use a narrow connection width if true; if not provided,
# uses the value of ::Stockpile.narrow? in this connection
# manager.
def initialize(options = {})
@redis_options = options.fetch(:redis, {})
@narrow = !!options.fetch(:narrow, ::Stockpile.narrow?)
@namespace = options.fetch(:namespace) {
@redis_options.fetch(:namespace) {
ENV['REDIS_NAMESPACE'] ||
(defined?(::Rails) && ::Rails.env) ||
ENV['RACK_ENV']
}
}
if @redis_options.has_key?(:namespace)
@redis_options = @redis_options.reject { |k, _| k == :namespace }
end
@connection = nil
@clients = {}
end
# The current primary connection to Redis.
attr_reader :connection
# Indicates if this connection manager is using a narrow connection
# width.
def narrow?
@narrow
end
# Connect to Redis, unless already connected. Additional client connections
# can be specified in the parameters as a shorthand for calls to
# #connection_for.
#
# If #narrow? is true, the same Redis connection will be used for all
# clients managed by this connection manager.
#
# manager.connect
# manager.connection_for(:redis)
#
# # This means the same as above.
# manager.connect(:redis)
def connect(*client_names)
@connection ||= connect_for_any
clients_from(*client_names).each { |client_name|
connection_for(client_name)
}
connection
end
# Perform a client connection to Redis for a specific +client_name+. The
# +client_name+ of +:all+ will always return +nil+.
#
# If the requested client does not yet exist, the connection will be
# created.
#
# Because Resque depends on Redis::Namespace, #connection_for will perform
# special Redis::Namespace handling for a connection with the name
# +:resque+.
#
# If #narrow? is true, the same Redis connection will be shared between all
# clients.
#
# If a connection has not yet been made, it will be made.
def connection_for(client_name)
connect unless connection
return nil if client_name == :all
@clients[client_name] ||= case client_name
when :resque
connect_for_resque
else
connect_for_any
end
end
# Reconnect to Redis for some or all clients. The primary connection will
# always be reconnected; other clients will be reconnected based on the
# +clients+ provided. Only clients actively managed by previous calls to
# #connect or #connection_for will be reconnected.
#
# If #reconnect is called with the value +:all+, all currently managed
# clients will be reconnected.
#
# If #narrow? is true, only the primary connection will be reconnected.
def reconnect(*client_names)
return unless connection
connection.client.reconnect
unless narrow?
clients_from(*client_names).each { |client_name|
redis = @clients[client_name]
redis.client.reconnect if redis
}
end
connection
end
# Disconnect from Redis for some or all clients. The primary connection
# will always be disconnected; other clients will be disconnected based on
# the +clients+ provided. Only clients actively managed by previous calls
# to #connect or #connection_for will be disconnected.
#
# If #disconnect is called with the value +:all+, all currently managed
# clients will be disconnected.
#
# If #narrow? is true, only the primary connection will be disconnected.
def disconnect(*client_names)
return unless connection
unless narrow?
clients_from(*client_names).each { |client_name|
redis = @clients[client_name]
redis.quit if redis
}
end
connection.quit
end
private
def clients_from(*client_names)
if client_names.size == 1
if client_names.first == :all
@clients.keys
else
client_names
end
else
client_names
end
end
def connect_for_any
return connection if connection && narrow?
r = ::Redis.new(@redis_options)
if @namespace
r = ::Redis::Namespace.new(@namespace, redis: r)
end
r
end
def connect_for_resque
r = connect_for_any
if r.instance_of?(::Redis::Namespace) && r.namespace.to_s !~ /:resque\z/
r = ::Redis::Namespace.new(:"#{r.namespace}:resque", redis: r.redis)
elsif r.instance_of?(::Redis)
r = ::Redis::Namespace.new("resque", redis: r)
end
r
end
end
# Enables module or class +mod+ to contain a Stockpile instance that defaults
# the created +cache+ method to using Stockpile::Redis as the key-value store
# connection manager.
#
# See Stockpile.inject!.
def self.inject_redis!(mod, options = {})
inject!(mod, options.merge(default_manager: Stockpile::Redis))
end
end