class ActiveSupport::Cache::RedisStore

Constants

DEFAULT_ERROR_HANDLER
ERRORS_TO_RESCUE

Attributes

data[R]

Public Class Methods

new(*addresses) click to toggle source

Instantiate the store.

Example:

RedisStore.new
  # => host: localhost,   port: 6379,  db: 0

RedisStore.new client: Redis.new(url: "redis://127.0.0.1:6380/1")
  # => host: localhost,   port: 6379,  db: 0

RedisStore.new "redis://example.com"
  # => host: example.com, port: 6379,  db: 0

RedisStore.new "redis://example.com:23682"
  # => host: example.com, port: 23682, db: 0

RedisStore.new "redis://example.com:23682/1"
  # => host: example.com, port: 23682, db: 1

RedisStore.new "redis://example.com:23682/1/theplaylist"
  # => host: example.com, port: 23682, db: 1, namespace: theplaylist

RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0"
  # => instantiate a cluster

RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0", pool_size: 5, pool_timeout: 10
  # => use a ConnectionPool

RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0",
  pool: ::ConnectionPool.new(size: 1, timeout: 1) { ::Redis::Store::Factory.create("localhost:6379/0") })
  # => supply an existing connection pool (e.g. for use with redis-sentinel or redis-failover)
Calls superclass method
# File lib/active_support/cache/redis_store.rb, line 54
def initialize(*addresses)
  @options = addresses.extract_options!
  addresses = addresses.compact.map(&:dup)

  @data = if @options[:pool]
            raise "pool must be an instance of ConnectionPool" unless @options[:pool].is_a?(ConnectionPool)
            @pooled = true
            @options[:pool]
          elsif [:pool_size, :pool_timeout].any? { |key| @options.has_key?(key) }
            pool_options           = {}
            pool_options[:size]    = options[:pool_size] if options[:pool_size]
            pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout]
            @pooled = true
            ::ConnectionPool.new(pool_options) { ::Redis::Store::Factory.create(*addresses, @options) }
          elsif @options[:client]
            @options[:client]
          else
            ::Redis::Store::Factory.create(*addresses, @options)
          end

  @error_handler = @options[:error_handler] || DEFAULT_ERROR_HANDLER

  super(@options)
end

Public Instance Methods

clear() click to toggle source

Clear all the data from the store.

# File lib/active_support/cache/redis_store.rb, line 247
def clear
  instrument(:clear, nil, nil) do
    failsafe(:clear) do
      with(&:flushdb)
    end
  end
end
decrement(name, amount = 1, options = {}) click to toggle source

Decrement a key in the store

If the key doesn't exist it will be initialized on 0. If the key exist but it isn't a Fixnum it will be initialized on 0.

Example:

We have two objects in cache:
  counter # => 23
  rabbit  # => #<Rabbit:0x5eee6c>

cache.decrement "counter"
cache.read "counter", :raw => true      # => "22"

cache.decrement "counter", 2
cache.read "counter", :raw => true      # => "20"

cache.decrement "a counter"
cache.read "a counter", :raw => true    # => "-1"

cache.decrement "rabbit"
cache.read "rabbit", :raw => true       # => "-1"
# File lib/active_support/cache/redis_store.rb, line 226
def decrement(name, amount = 1, options = {})
  instrument :decrement, name, amount: amount do
    failsafe :decrement do
      options = merged_options(options)
      key = normalize_key(name, options)

      with do |c|
        c.decrby(key, amount).tap do
          write_key_expiry(c, key, options)
        end
      end
    end
  end
end
delete_matched(matcher, options = nil) click to toggle source

Delete objects for matched keys.

Performance note: this operation can be dangerous for large production databases, as it uses the Redis “KEYS” command, which is O(N) over the total number of keys in the database. Users of large Redis caches should avoid this method.

Example:

cache.delete_matched "rab*"
# File lib/active_support/cache/redis_store.rb, line 99
def delete_matched(matcher, options = nil)
  options = merged_options(options)
  instrument(:delete_matched, matcher.inspect) do
    failsafe(:read_multi, returning: false) do
      matcher = key_matcher(matcher, options)
      begin
        with do |store|
          !(keys = store.keys(matcher)).empty? && store.del(*keys)
        end
      end
    end
  end
end
exist?(name, options = nil) click to toggle source

fixed problem with invalid exists? method github.com/rails/rails/commit/cad2c8f5791d5bd4af0f240d96e00bae76eabd2f

Calls superclass method
# File lib/active_support/cache/redis_store.rb, line 257
def exist?(name, options = nil)
  res = super(name, options)
  res || false
end
expire(key, ttl) click to toggle source
# File lib/active_support/cache/redis_store.rb, line 241
def expire(key, ttl)
  options = merged_options(nil)
  with { |c| c.expire normalize_key(key, options), ttl }
end
fetch_multi(*names) { |name| ... } click to toggle source
# File lib/active_support/cache/redis_store.rb, line 139
def fetch_multi(*names)
  options = names.extract_options!
  return {} if names == []

  results = read_multi(*names, options)
  need_writes = {}

  fetched = names.inject({}) do |memo, name|
    memo[name] = results.fetch(name) do
      value = yield name
      need_writes[name] = value
      value
    end

    memo
  end

  failsafe(:fetch_multi_write) do
    with do |c|
      c.multi do
        need_writes.each do |name, value|
          write(name, value, options)
        end
      end
    end
  end

  fetched
end
increment(name, amount = 1, options = {}) click to toggle source

Increment a key in the store.

If the key doesn't exist it will be initialized on 0. If the key exist but it isn't a Fixnum it will be initialized on 0.

Example:

We have two objects in cache:
  counter # => 23
  rabbit  # => #<Rabbit:0x5eee6c>

cache.increment "counter"
cache.read "counter", :raw => true      # => "24"

cache.increment "counter", 6
cache.read "counter", :raw => true      # => "30"

cache.increment "a counter"
cache.read "a counter", :raw => true    # => "1"

cache.increment "rabbit"
cache.read "rabbit", :raw => true       # => "1"
# File lib/active_support/cache/redis_store.rb, line 190
def increment(name, amount = 1, options = {})
  instrument :increment, name, amount: amount do
    failsafe :increment do
      options = merged_options(options)
      key = normalize_key(name, options)

      with do |c|
        c.incrby(key, amount).tap do
          write_key_expiry(c, key, options)
        end
      end
    end
  end
end
read_multi(*names) click to toggle source

Reads multiple keys from the cache using a single call to the servers for all keys. Options can be passed in the last argument.

Example:

cache.read_multi "rabbit", "white-rabbit"
cache.read_multi "rabbit", "white-rabbit", :raw => true
# File lib/active_support/cache/redis_store.rb, line 119
def read_multi(*names)
  options = names.extract_options!
  return {} if names == []

  keys = names.map{|name| normalize_key(name, options)}
  args = [keys, options]
  args.flatten!

  instrument(:read_multi, names) do |payload|
    failsafe(:read_multi, returning: {}) do
      values = with { |c| c.mget(*args) }
      values.map! { |v| v.is_a?(ActiveSupport::Cache::Entry) ? v.value : v }

      Hash[names.zip(values)].reject{|k,v| v.nil?}.tap do |result|
        payload[:hits] = result.keys if payload
      end
    end
  end
end
reconnect() click to toggle source
# File lib/active_support/cache/redis_store.rb, line 274
def reconnect
  @data.reconnect if @data.respond_to?(:reconnect)
end
stats() click to toggle source
# File lib/active_support/cache/redis_store.rb, line 262
def stats
  with(&:info)
end
with(&block) click to toggle source
# File lib/active_support/cache/redis_store.rb, line 266
def with(&block)
  if defined?(@pooled) && @pooled
    @data.with(&block)
  else
    block.call(@data)
  end
end
write(name, value, options = nil) click to toggle source
# File lib/active_support/cache/redis_store.rb, line 79
def write(name, value, options = nil)
  options = merged_options(options.to_h.symbolize_keys)
  instrument(:write, name, options) do |_payload|
    if options[:expires_in].present? && options[:race_condition_ttl].present? && options[:raw].blank?
      options[:expires_in] = options[:expires_in].to_f + options[:race_condition_ttl].to_f
    end
    entry = options[:raw].present? ? value : Entry.new(value, **options)
    write_entry(normalize_key(name, options), entry, options)
  end
end

Protected Instance Methods

delete_entry(key, options) click to toggle source

Implement the ActiveSupport::Cache#delete_entry

It's really needed and use

# File lib/active_support/cache/redis_store.rb, line 305
def delete_entry(key, options)
  failsafe(:delete_entry, returning: false) do
    with { |c| c.del key }
  end
end
key_matcher(pattern, options) click to toggle source

Add the namespace defined in the options to a pattern designed to match keys.

This implementation is __different__ than ActiveSupport: __it doesn't accept Regular expressions__, because the Redis matcher is designed only for strings with wildcards.

# File lib/active_support/cache/redis_store.rb, line 320
def key_matcher(pattern, options)
  prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace]

  pattern = pattern.inspect[1..-2] if pattern.is_a? Regexp

  if prefix
    "#{prefix}:#{pattern}"
  else
    pattern
  end
end
raise_errors?() click to toggle source
# File lib/active_support/cache/redis_store.rb, line 311
def raise_errors?
  !!@options[:raise_errors]
end
read_entry(key, options) click to toggle source
# File lib/active_support/cache/redis_store.rb, line 286
def read_entry(key, options)
  failsafe(:read_entry) do
    entry = with { |c| c.get key, options }
    return unless entry
    entry.is_a?(Entry) ? entry : Entry.new(entry)
  end
end
write_entry(key, entry, options) click to toggle source
# File lib/active_support/cache/redis_store.rb, line 279
def write_entry(key, entry, options)
  failsafe(:write_entry, returning: false) do
    method = options && options[:unless_exist] ? :setnx : :set
    with { |client| client.send method, key, entry, options }
  end
end
write_key_expiry(client, key, options) click to toggle source
# File lib/active_support/cache/redis_store.rb, line 294
def write_key_expiry(client, key, options)
  if options[:expires_in] && client.ttl(key) < 0
    client.expire key, options[:expires_in].to_i
  end
end

Private Instance Methods

failsafe(method, returning: nil) { || ... } click to toggle source
# File lib/active_support/cache/redis_store.rb, line 339
def failsafe(method, returning: nil)
  yield
rescue ::Redis::BaseConnectionError => e
  raise if raise_errors?
  handle_exception(exception: e, method: method, returning: returning)
  returning
end
handle_exception(exception: nil, method: nil, returning: nil) click to toggle source
# File lib/active_support/cache/redis_store.rb, line 347
def handle_exception(exception: nil, method: nil, returning: nil)
  if @error_handler
    @error_handler.(method: method, exception: exception, returning: returning)
  end
rescue => failsafe
  warn("RedisStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n  #{failsafe.backtrace.join("\n  ")}")
end
normalize_key(*args) click to toggle source
# File lib/active_support/cache/redis_store.rb, line 334
def normalize_key(*args)
  namespaced_key(*args)
end