class Redis::Namespace

Constants

ADMINISTRATIVE_COMMANDS
COMMANDS
DEPRECATED_COMMANDS
Enumerator

Support 1.8.7 by providing a namespaced reference to Enumerable::Enumerator

HELPER_COMMANDS
NAMESPACED_COMMANDS

The following tables define how input parameters and result values should be modified for the namespace.

COMMANDS is a hash. Each key is the name of a command and each value is a two element array.

The first element in the value array describes how to modify the arguments passed. It can be one of:

nil
  Do nothing.
:first
  Add the namespace to the first argument passed, e.g.
    GET key => GET namespace:key
:all
  Add the namespace to all arguments passed, e.g.
    MGET key1 key2 => MGET namespace:key1 namespace:key2
:exclude_first
  Add the namespace to all arguments but the first, e.g.
:exclude_last
  Add the namespace to all arguments but the last, e.g.
    BLPOP key1 key2 timeout =>
    BLPOP namespace:key1 namespace:key2 timeout
:exclude_options
  Add the namespace to all arguments, except the last argument,
  if the last argument is a hash of options.
    ZUNIONSTORE key1 2 key2 key3 WEIGHTS 2 1 =>
    ZUNIONSTORE namespace:key1 2 namespace:key2 namespace:key3 WEIGHTS 2 1
:alternate
  Add the namespace to every other argument, e.g.
    MSET key1 value1 key2 value2 =>
    MSET namespace:key1 value1 namespace:key2 value2
:sort
  Add namespace to first argument if it is non-nil
  Add namespace to second arg's :by and :store if second arg is a Hash
  Add namespace to each element in second arg's :get if second arg is
    a Hash; forces second arg's :get to be an Array if present.
:eval_style
  Add namespace to each element in keys argument (via options hash or multi-args)
:scan_style
  Add namespace to :match option, or supplies "#{namespace}:*" if not present.

The second element in the value array describes how to modify the return value of the Redis call. It can be one of:

nil
  Do nothing.
:all
  Add the namespace to all elements returned, e.g.
    key1 key2 => namespace:key1 namespace:key2
TRANSACTION_COMMANDS
VERSION

Attributes

namespace[W]
redis[R]
warning[RW]

Public Class Methods

new(namespace, options = {}) click to toggle source
# File lib/redis/namespace.rb, line 243
def initialize(namespace, options = {})
  @namespace = namespace
  @redis = options[:redis] || Redis.current
  @warning = !!options.fetch(:warning) do
               !ENV['REDIS_NAMESPACE_QUIET']
             end
  @deprecations = !!options.fetch(:deprecations) do
                    ENV['REDIS_NAMESPACE_DEPRECATIONS']
                  end
  @has_new_client_method = @redis.respond_to?(:_client)
end

Public Instance Methods

_client() click to toggle source
# File lib/redis/namespace.rb, line 269
def _client
  @has_new_client_method ? @redis._client : @redis.client # for redis-4.0.0
end
call_with_namespace(command, *args, &block) click to toggle source
# File lib/redis/namespace.rb, line 397
def call_with_namespace(command, *args, &block)
  handling = COMMANDS[command.to_s.downcase]

  if handling.nil?
    fail("Redis::Namespace does not know how to handle '#{command}'.")
  end

  (before, after) = handling

  # Modify the local *args array in-place, no need to copy it.
  args.map! {|arg| clone_args(arg)}

  # Add the namespace to any parameters that are keys.
  case before
  when :first
    args[0] = add_namespace(args[0]) if args[0]
    args[-1] = ruby2_keywords_hash(args[-1]) if args[-1].is_a?(Hash)
  when :all
    args = add_namespace(args)
  when :exclude_first
    first = args.shift
    args = add_namespace(args)
    args.unshift(first) if first
  when :exclude_last
    last = args.pop unless args.length == 1
    args = add_namespace(args)
    args.push(last) if last
  when :exclude_options
    if args.last.is_a?(Hash)
      last = ruby2_keywords_hash(args.pop)
      args = add_namespace(args)
      args.push(last)
    else
      args = add_namespace(args)
    end
  when :alternate
    args.each_with_index { |a, i| args[i] = add_namespace(a) if i.even? }
  when :sort
    args[0] = add_namespace(args[0]) if args[0]
    if args[1].is_a?(Hash)
      [:by, :store].each do |key|
        args[1][key] = add_namespace(args[1][key]) if args[1][key]
      end

      args[1][:get] = Array(args[1][:get])

      args[1][:get].each_index do |i|
        args[1][:get][i] = add_namespace(args[1][:get][i]) unless args[1][:get][i] == "#"
      end
      args[1] = ruby2_keywords_hash(args[1])
    end
  when :eval_style
    # redis.eval() and evalsha() can either take the form:
    #
    #   redis.eval(script, [key1, key2], [argv1, argv2])
    #
    # Or:
    #
    #   redis.eval(script, :keys => ['k1', 'k2'], :argv => ['arg1', 'arg2'])
    #
    # This is a tricky + annoying special case, where we only want the `keys`
    # argument to be namespaced.
    if args.last.is_a?(Hash)
      args.last[:keys] = add_namespace(args.last[:keys])
    else
      args[1] = add_namespace(args[1])
    end
  when :scan_style
    options = (args.last.kind_of?(Hash) ? args.pop : {})
    options[:match] = add_namespace(options.fetch(:match, '*'))
    args << ruby2_keywords_hash(options)

    if block
      original_block = block
      block = proc { |key| original_block.call rem_namespace(key) }
    end
  end

  # Dispatch the command to Redis and store the result.
  result = @redis.send(command, *args, &block)

  # Don't try to remove namespace from a Redis::Future, you can't.
  return result if result.is_a?(Redis::Future)

  # Remove the namespace from results that are keys.
  case after
  when :all
    result = rem_namespace(result)
  when :first
    result[0] = rem_namespace(result[0]) if result
  when :second
    result[1] = rem_namespace(result[1]) if result
  end

  result
end
client() click to toggle source
# File lib/redis/namespace.rb, line 263
def client
  warn("The client method is deprecated as of redis-rb 4.0.0, please use the new _client" +
        "method instead. Support for the old method will be removed in redis-namespace 2.0.") if @has_new_client_method && deprecations?
  _client
end
connection() click to toggle source
# File lib/redis/namespace.rb, line 317
def connection
  @redis.connection.tap { |info| info[:namespace] = @namespace }
end
deprecations?() click to toggle source
# File lib/redis/namespace.rb, line 255
def deprecations?
  @deprecations
end
eval(*args) click to toggle source
# File lib/redis/namespace.rb, line 325
def eval(*args)
  call_with_namespace(:eval, *args)
end
exec() click to toggle source
# File lib/redis/namespace.rb, line 321
def exec
  call_with_namespace(:exec)
end
full_namespace() click to toggle source
# File lib/redis/namespace.rb, line 313
def full_namespace
  redis.is_a?(Namespace) ? "#{redis.full_namespace}:#{namespace}" : namespace.to_s
end
inspect() click to toggle source
# File lib/redis/namespace.rb, line 379
def inspect
  "<#{self.class.name} v#{VERSION} with client v#{Redis::VERSION} "\
  "for #{@redis.id}/#{full_namespace}>"
end
keys(query = nil) click to toggle source
# File lib/redis/namespace.rb, line 288
def keys(query = nil)
  call_with_namespace(:keys, query || '*')
end
method_missing(command, *args, &block) click to toggle source
Calls superclass method
# File lib/redis/namespace.rb, line 357
def method_missing(command, *args, &block)
  normalized_command = command.to_s.downcase

  if COMMANDS.include?(normalized_command)
    send(normalized_command, *args, &block)
  elsif @redis.respond_to?(normalized_command) && !deprecations?
    # blind passthrough is deprecated and will be removed in 2.0
    # redis-namespace does not know how to handle this command.
    # Passing it to @redis as is, where redis-namespace shows
    # a warning message if @warning is set.
    if warning?
      warn("Passing '#{command}' command to redis as is; blind " +
           "passthrough has been deprecated and will be removed in " +
           "redis-namespace 2.0 (at #{call_site})")
    end
    @redis.send(command, *args, &block)
  else
    super
  end
end
multi(&block) click to toggle source
# File lib/redis/namespace.rb, line 292
def multi(&block)
  if block_given?
    namespaced_block(:multi, &block)
  else
    call_with_namespace(:multi)
  end
end
namespace(desired_namespace = nil) { |namespace(desired_namespace, :redis => redis)| ... } click to toggle source
# File lib/redis/namespace.rb, line 304
def namespace(desired_namespace = nil)
  if desired_namespace
    yield Redis::Namespace.new(desired_namespace,
                               :redis => @redis)
  end

  @namespace
end
pipelined(&block) click to toggle source
# File lib/redis/namespace.rb, line 300
def pipelined(&block)
  namespaced_block(:pipelined, &block)
end
respond_to?(command, include_private=false) click to toggle source

emulate Ruby 1.9+ and keep respond_to_missing? logic together.

Calls superclass method
# File lib/redis/namespace.rb, line 282
def respond_to?(command, include_private=false)
  return !deprecations? if DEPRECATED_COMMANDS.include?(command.to_s.downcase)

  respond_to_missing?(command, include_private) or super
end
Also aliased as: self_respond_to?
respond_to_missing?(command, include_all=false) click to toggle source
Calls superclass method
# File lib/redis/namespace.rb, line 384
def respond_to_missing?(command, include_all=false)
  normalized_command = command.to_s.downcase

  case
  when COMMANDS.include?(normalized_command)
    true
  when !deprecations? && redis.respond_to?(command, include_all)
    true
  else
    defined?(super) && super
  end
end
self_respond_to?(command, include_private=false)
Alias for: respond_to?
type(key) click to toggle source

Ruby defines a now deprecated type method so we need to override it here since it will never hit method_missing

# File lib/redis/namespace.rb, line 275
def type(key)
  call_with_namespace(:type, key)
end
warning?() click to toggle source
# File lib/redis/namespace.rb, line 259
def warning?
  @warning
end

Private Instance Methods

add_namespace(key) click to toggle source
# File lib/redis/namespace.rb, line 533
def add_namespace(key)
  return key unless key && @namespace

  case key
  when Array
    key.map! {|k| add_namespace k}
  when Hash
    key.keys.each {|k| key[add_namespace(k)] = key.delete(k)}
    key
  else
    "#{@namespace}:#{key}"
  end
end
call_site() click to toggle source
# File lib/redis/namespace.rb, line 518
def call_site
  caller.reject { |l| l.start_with?(__FILE__) }.first
end
clone_args(arg) click to toggle source

Avoid modifying the caller’s (pass-by-reference) arguments.

# File lib/redis/namespace.rb, line 508
def clone_args(arg)
  if arg.is_a?(Array)
    arg.map {|sub_arg| clone_args(sub_arg)}
  elsif arg.is_a?(Hash)
    Hash[arg.map {|k, v| [clone_args(k), clone_args(v)]}]
  else
    arg # Some objects (e.g. symbol) can't be dup'd.
  end
end
create_enumerator(&block) click to toggle source
# File lib/redis/namespace.rb, line 564
def create_enumerator(&block)
  # Enumerator in 1.8.7 *requires* a single argument, so we need to use
  # its Generator class, which matches the block syntax of 1.9.x's
  # Enumerator class.
  if RUBY_VERSION.start_with?('1.8')
    require 'generator' unless defined?(Generator)
    Generator.new(&block).to_enum
  else
    Enumerator.new(&block)
  end
end
namespaced_block(command) { |self| ... } click to toggle source
# File lib/redis/namespace.rb, line 522
def namespaced_block(command, &block)
  redis.send(command) do |r|
    begin
      original, @redis = @redis, r
      yield self
    ensure
      @redis = original
    end
  end
end
rem_namespace(key) click to toggle source
# File lib/redis/namespace.rb, line 547
def rem_namespace(key)
  return key unless key && @namespace

  case key
  when Array
    key.map {|k| rem_namespace k}
  when Hash
    Hash[*key.map {|k, v| [ rem_namespace(k), v ]}.flatten]
  when Enumerator
    create_enumerator do |yielder|
      key.each { |k| yielder.yield rem_namespace(k) }
    end
  else
    key.to_s.sub(/\A#{@namespace}:/, '')
  end
end
ruby2_keywords_hash(kwargs) click to toggle source
# File lib/redis/namespace.rb, line 498
def ruby2_keywords_hash(kwargs)
  Hash.ruby2_keywords_hash(kwargs)
end