redis-namespace-1.8.1/0000755000004100000410000000000014027652657014627 5ustar www-datawww-dataredis-namespace-1.8.1/README.md0000644000004100000410000000776614027652657016126 0ustar www-datawww-dataredis-namespace =============== Redis::Namespace provides an interface to a namespaced subset of your [redis][] keyspace (e.g., keys with a common beginning), and requires the [redis-rb][] gem. ```ruby require 'redis-namespace' # => true redis_connection = Redis.new # => # namespaced_redis = Redis::Namespace.new(:ns, redis: redis_connection) # => # namespaced_redis.set('foo', 'bar') # redis_connection.set('ns:foo', 'bar') # => "OK" # Redis::Namespace automatically prepended our namespace to the key # before sending it to our redis client. namespaced_redis.get('foo') # => "bar" redis_connection.get('foo') # => nil redis_connection.get('ns:foo') # => "bar" namespaced_redis.del('foo') # => 1 namespaced_redis.get('foo') # => nil redis_connection.get('ns:foo') # => nil ``` Installation ============ Redis::Namespace is packaged as the redis-namespace gem, and hosted on rubygems.org. From the command line: $ gem install redis-namespace Or in your Gemfile: ```ruby gem 'redis-namespace' ``` Caveats ======= `Redis::Namespace` provides a namespaced interface to `Redis` by keeping an internal registry of the method signatures in `Redis` provided by the [redis-rb][] gem; we keep track of which arguments need the namespace added, and which return values need the namespace removed. Blind Passthrough ----------------- If your version of this gem doesn't know about a particular command, it can't namespace it. Historically, this has meant that Redis::Namespace blindly passes unknown commands on to the underlying redis connection without modification which can lead to surprising effects. As of v1.5.0, blind passthrough has been deprecated, and the functionality will be removed entirely in 2.0. If you come across a command that is not yet supported, please open an issue on the [issue tracker][] or submit a pull-request. Administrative Commands ----------------------- The effects of some redis commands cannot be limited to a particular namespace (e.g., `FLUSHALL`, which literally truncates all databases in your redis server, regardless of keyspace). Historically, this has meant that Redis::Namespace intentionally passes administrative commands on to the underlying redis connection without modification, which can lead to surprising effects. As of v1.6.0, the direct use of administrative commands has been deprecated, and the functionality will be removed entirely in 2.0; while such commands are often useful for testing or administration, their meaning is inherently hidden when placed behind an interface that implies it will namespace everything. The prefered way to send an administrative command is on the redis connection itself, which is publicly exposed as `Redis::Namespace#redis`: ```ruby namespaced.redis.flushall() # => "OK" ``` 2.x Planned Breaking Changes ============================ As mentioned above, 2.0 will remove blind passthrough and the administrative command passthrough. By default in 1.5+, deprecation warnings are present and enabled; they can be silenced by initializing `Redis::Namespace` with `warnings: false` or by setting the `REDIS_NAMESPACE_QUIET` environment variable. Early opt-in ------------ To enable testing against the 2.x interface before its release, in addition to deprecation warnings, early opt-in to these changes can be enabled by initializing `Redis::Namespace` with `deprecations: true` or by setting the `REDIS_NAMESPACE_DEPRECATIONS` environment variable. This should only be done once all warnings have been addressed. Authors ======= While there are many authors who have contributed to this project, the following have done so on an ongoing basis with at least 5 commits: - Chris Wanstrath (@defunkt) - Ryan Biesemeyer (@yaauie) - Steve Klabnik (@steveklabnik) - Terence Lee (@hone) - Eoin Coffey (@ecoffey) [redis]: http://redis.io [redis-rb]: https://github.com/redis/redis-rb [issue tracker]: https://github.com/resque/redis-namespace/issues redis-namespace-1.8.1/spec/0000755000004100000410000000000014027652657015561 5ustar www-datawww-dataredis-namespace-1.8.1/spec/spec_helper.rb0000644000004100000410000000277514027652657020412 0ustar www-datawww-datarequire 'rubygems' require 'bundler' Bundler.setup(:default, :test) Bundler.require(:default, :test) require 'rspec' require 'rspec/its' require 'redis' require 'logger' $TESTING=true $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') require 'redis/namespace' if Redis.respond_to?(:exists_returns_integer=) Redis.exists_returns_integer = true end module Helper def capture_stderr(io = nil) require 'stringio' io ||= StringIO.new begin original, $stderr = $stderr, io yield rescue Redis::CommandError # ignore Redis::CommandError for test and # return captured messages $stderr.string.chomp ensure $stderr = original end end def with_env(env = {}) backup_env = ENV.to_hash.dup ENV.update(env) yield ensure ENV.replace(backup_env) end def silent verbose, $VERBOSE = $VERBOSE, false begin yield ensure $VERBOSE = verbose end end def with_external_encoding(encoding) original_encoding = Encoding.default_external begin silent { Encoding.default_external = Encoding.find(encoding) } yield ensure silent { Encoding.default_external = original_encoding } end end def try_encoding(encoding, &block) if defined?(Encoding) with_external_encoding(encoding, &block) else yield end end end RSpec.configure do |c| c.include Helper end RSpec::Matchers.define :have_key do |expected| match do |redis| redis.exists(expected) end end redis-namespace-1.8.1/spec/redis_spec.rb0000644000004100000410000010362514027652657020235 0ustar www-datawww-data# encoding: utf-8 require File.dirname(__FILE__) + '/spec_helper' describe "redis" do @redis_version = Gem::Version.new(Redis.current.info["redis_version"]) let(:redis_client) { @redis.respond_to?(:_client) ? @redis._client : @redis.client} before(:all) do # use database 15 for testing so we dont accidentally step on your real data @redis = Redis.new :db => 15 end before(:each) do @namespaced = Redis::Namespace.new(:ns, :redis => @redis) @redis.flushdb @redis.set('foo', 'bar') end after(:each) do @redis.flushdb end after(:all) do @redis.quit end # redis-rb 3.3.4+ it "should inject :namespace into connection info" do info = @redis.connection.merge(:namespace => :ns) expect(@namespaced.connection).to eq(info) end it "proxies `client` to the _client and deprecated" do expect(@namespaced.client).to eq(redis_client) end it "proxies `_client` to the _client" do expect(@namespaced._client).to eq(redis_client) end it "should be able to use a namespace" do expect(@namespaced.get('foo')).to eq(nil) @namespaced.set('foo', 'chris') expect(@namespaced.get('foo')).to eq('chris') @redis.set('foo', 'bob') expect(@redis.get('foo')).to eq('bob') @namespaced.incrby('counter', 2) expect(@namespaced.get('counter').to_i).to eq(2) expect(@redis.get('counter')).to eq(nil) expect(@namespaced.type('counter')).to eq('string') end context 'when sending capital commands (issue 68)' do it 'should be able to use a namespace' do @namespaced.send('SET', 'fubar', 'quux') expect(@redis.get('fubar')).to be_nil expect(@namespaced.get('fubar')).to eq 'quux' end end it "should be able to use a namespace with bpop" do @namespaced.rpush "foo", "string" @namespaced.rpush "foo", "ns:string" @namespaced.rpush "foo", "string_no_timeout" expect(@namespaced.blpop("foo", 1)).to eq(["foo", "string"]) expect(@namespaced.blpop("foo", 1)).to eq(["foo", "ns:string"]) expect(@namespaced.blpop("foo")).to eq(["foo", "string_no_timeout"]) expect(@namespaced.blpop("foo", 1)).to eq(nil) end it "should be able to use a namespace with del" do @namespaced.set('foo', 1000) @namespaced.set('bar', 2000) @namespaced.set('baz', 3000) @namespaced.del 'foo' expect(@namespaced.get('foo')).to eq(nil) @namespaced.del 'bar', 'baz' expect(@namespaced.get('bar')).to eq(nil) expect(@namespaced.get('baz')).to eq(nil) end it "should be able to use a namespace with unlink" do @namespaced.set('foo', 1000) @namespaced.set('bar', 2000) @namespaced.set('baz', 3000) @namespaced.unlink 'foo' expect(@namespaced.get('foo')).to eq(nil) @namespaced.unlink 'bar', 'baz' expect(@namespaced.get('bar')).to eq(nil) expect(@namespaced.get('baz')).to eq(nil) end it 'should be able to use a namespace with append' do @namespaced.set('foo', 'bar') expect(@namespaced.append('foo','n')).to eq(4) expect(@namespaced.get('foo')).to eq('barn') expect(@redis.get('foo')).to eq('bar') end it 'should be able to use a namespace with brpoplpush' do @namespaced.lpush('foo','bar') expect(@namespaced.brpoplpush('foo','bar',0)).to eq('bar') expect(@namespaced.lrange('foo',0,-1)).to eq([]) expect(@namespaced.lrange('bar',0,-1)).to eq(['bar']) end it 'should be able to use a namespace with getbit' do @namespaced.set('foo','bar') expect(@namespaced.getbit('foo',1)).to eq(1) end it 'should be able to use a namespace with getrange' do @namespaced.set('foo','bar') expect(@namespaced.getrange('foo',0,-1)).to eq('bar') end it 'should be able to use a namespace with linsert' do @namespaced.rpush('foo','bar') @namespaced.rpush('foo','barn') @namespaced.rpush('foo','bart') expect(@namespaced.linsert('foo','BEFORE','barn','barf')).to eq(4) expect(@namespaced.lrange('foo',0,-1)).to eq(['bar','barf','barn','bart']) end it 'should be able to use a namespace with lpushx' do expect(@namespaced.lpushx('foo','bar')).to eq(0) @namespaced.lpush('foo','boo') expect(@namespaced.lpushx('foo','bar')).to eq(2) expect(@namespaced.lrange('foo',0,-1)).to eq(['bar','boo']) end it 'should be able to use a namespace with rpushx' do expect(@namespaced.rpushx('foo','bar')).to eq(0) @namespaced.lpush('foo','boo') expect(@namespaced.rpushx('foo','bar')).to eq(2) expect(@namespaced.lrange('foo',0,-1)).to eq(['boo','bar']) end it 'should be able to use a namespace with setbit' do @namespaced.setbit('virgin_key', 1, 1) expect(@namespaced.exists?('virgin_key')).to be true expect(@namespaced.get('virgin_key')).to eq(@namespaced.getrange('virgin_key',0,-1)) end it 'should be able to use a namespace with exists' do @namespaced.set('foo', 1000) @namespaced.set('bar', 2000) expect(@namespaced.exists('foo', 'bar')).to eq(2) end it 'should be able to use a namespace with exists?' do @namespaced.set('foo', 1000) @namespaced.set('bar', 2000) expect(@namespaced.exists?('does_not_exist', 'bar')).to eq(true) end it 'should be able to use a namespace with bitpos' do @namespaced.setbit('bit_map', 42, 1) expect(@namespaced.bitpos('bit_map', 0)).to eq(0) expect(@namespaced.bitpos('bit_map', 1)).to eq(42) end it 'should be able to use a namespace with setrange' do @namespaced.setrange('foo', 0, 'bar') expect(@namespaced.get('foo')).to eq('bar') @namespaced.setrange('bar', 2, 'foo') expect(@namespaced.get('bar')).to eq("\000\000foo") end it "should be able to use a namespace with mget" do @namespaced.set('foo', 1000) @namespaced.set('bar', 2000) expect(@namespaced.mapped_mget('foo', 'bar')).to eq({ 'foo' => '1000', 'bar' => '2000' }) expect(@namespaced.mapped_mget('foo', 'baz', 'bar')).to eq({'foo'=>'1000', 'bar'=>'2000', 'baz' => nil}) end it "should be able to use a namespace with mset" do @namespaced.mset('foo', '1000', 'bar', '2000') expect(@namespaced.mapped_mget('foo', 'bar')).to eq({ 'foo' => '1000', 'bar' => '2000' }) expect(@namespaced.mapped_mget('foo', 'baz', 'bar')).to eq({ 'foo' => '1000', 'bar' => '2000', 'baz' => nil}) @namespaced.mapped_mset('foo' => '3000', 'bar' => '5000') expect(@namespaced.mapped_mget('foo', 'bar')).to eq({ 'foo' => '3000', 'bar' => '5000' }) expect(@namespaced.mapped_mget('foo', 'baz', 'bar')).to eq({ 'foo' => '3000', 'bar' => '5000', 'baz' => nil}) end it "should be able to use a namespace with msetnx" do @namespaced.msetnx('foo', '1000', 'bar', '2000') expect(@namespaced.mapped_mget('foo', 'bar')).to eq({ 'foo' => '1000', 'bar' => '2000' }) expect(@namespaced.mapped_mget('foo', 'baz', 'bar')).to eq({ 'foo' => '1000', 'bar' => '2000', 'baz' => nil}) end it "should be able to use a namespace with mapped_msetnx" do @namespaced.set('foo','1') expect(@namespaced.mapped_msetnx('foo'=>'1000', 'bar'=>'2000')).to be false expect(@namespaced.mapped_mget('foo', 'bar')).to eq({ 'foo' => '1', 'bar' => nil }) expect(@namespaced.mapped_msetnx('bar'=>'2000', 'baz'=>'1000')).to be true expect(@namespaced.mapped_mget('foo', 'bar')).to eq({ 'foo' => '1', 'bar' => '2000' }) end it "should be able to use a namespace with hashes" do @namespaced.hset('foo', 'key', 'value') @namespaced.hset('foo', 'key1', 'value1') expect(@namespaced.hget('foo', 'key')).to eq('value') expect(@namespaced.hgetall('foo')).to eq({'key' => 'value', 'key1' => 'value1'}) expect(@namespaced.hlen('foo')).to eq(2) expect(@namespaced.hkeys('foo')).to eq(['key', 'key1']) @namespaced.hmset('bar', 'key', 'value', 'key1', 'value1') @namespaced.hmget('bar', 'key', 'key1') @namespaced.hmset('bar', 'a_number', 1) expect(@namespaced.hmget('bar', 'a_number')).to eq(['1']) @namespaced.hincrby('bar', 'a_number', 3) expect(@namespaced.hmget('bar', 'a_number')).to eq(['4']) expect(@namespaced.hgetall('bar')).to eq({'key' => 'value', 'key1' => 'value1', 'a_number' => '4'}) expect(@namespaced.hsetnx('foonx','nx',10)).to be true expect(@namespaced.hsetnx('foonx','nx',12)).to be false expect(@namespaced.hget('foonx','nx')).to eq("10") expect(@namespaced.hkeys('foonx')).to eq(%w{ nx }) expect(@namespaced.hvals('foonx')).to eq(%w{ 10 }) @namespaced.mapped_hmset('baz', {'key' => 'value', 'key1' => 'value1', 'a_number' => 4}) expect(@namespaced.mapped_hmget('baz', 'key', 'key1', 'a_number')).to eq({'key' => 'value', 'key1' => 'value1', 'a_number' => '4'}) expect(@namespaced.hgetall('baz')).to eq({'key' => 'value', 'key1' => 'value1', 'a_number' => '4'}) end it "should properly intersect three sets" do @namespaced.sadd('foo', 1) @namespaced.sadd('foo', 2) @namespaced.sadd('foo', 3) @namespaced.sadd('bar', 2) @namespaced.sadd('bar', 3) @namespaced.sadd('bar', 4) @namespaced.sadd('baz', 3) expect(@namespaced.sinter('foo', 'bar', 'baz')).to eq(%w( 3 )) end it "should properly union two sets" do @namespaced.sadd('foo', 1) @namespaced.sadd('foo', 2) @namespaced.sadd('bar', 2) @namespaced.sadd('bar', 3) @namespaced.sadd('bar', 4) expect(@namespaced.sunion('foo', 'bar').sort).to eq(%w( 1 2 3 4 )) end it "should properly union two sorted sets with options" do @namespaced.zadd('sort1', 1, 1) @namespaced.zadd('sort1', 2, 2) @namespaced.zadd('sort2', 2, 2) @namespaced.zadd('sort2', 3, 3) @namespaced.zadd('sort2', 4, 4) @namespaced.zunionstore('union', ['sort1', 'sort2'], weights: [2, 1]) expect(@namespaced.zrevrange('union', 0, -1)).to eq(%w( 2 4 3 1 )) end it "should properly union two sorted sets without options" do @namespaced.zadd('sort1', 1, 1) @namespaced.zadd('sort1', 2, 2) @namespaced.zadd('sort2', 2, 2) @namespaced.zadd('sort2', 3, 3) @namespaced.zadd('sort2', 4, 4) @namespaced.zunionstore('union', ['sort1', 'sort2']) expect(@namespaced.zrevrange('union', 0, -1)).to eq(%w( 4 2 3 1 )) end it "should properly intersect two sorted sets without options" do @namespaced.zadd('food', 1, 'orange') @namespaced.zadd('food', 2, 'banana') @namespaced.zadd('food', 3, 'eggplant') @namespaced.zadd('color', 2, 'orange') @namespaced.zadd('color', 3, 'yellow') @namespaced.zadd('color', 4, 'eggplant') @namespaced.zinterstore('inter', ['food', 'color']) inter_values = @namespaced.zrevrange('inter', 0, -1, :with_scores => true) expect(inter_values).to match_array([['orange', 3.0], ['eggplant', 7.0]]) end it "should properly intersect two sorted sets with options" do @namespaced.zadd('food', 1, 'orange') @namespaced.zadd('food', 2, 'banana') @namespaced.zadd('food', 3, 'eggplant') @namespaced.zadd('color', 2, 'orange') @namespaced.zadd('color', 3, 'yellow') @namespaced.zadd('color', 4, 'eggplant') @namespaced.zinterstore('inter', ['food', 'color'], :aggregate => "min") inter_values = @namespaced.zrevrange('inter', 0, -1, :with_scores => true) expect(inter_values).to match_array([['orange', 1.0], ['eggplant', 3.0]]) end it "should return lexicographical range for sorted set" do @namespaced.zadd('food', 0, 'orange') @namespaced.zadd('food', 0, 'banana') @namespaced.zadd('food', 0, 'eggplant') values = @namespaced.zrangebylex('food', '[b', '(o') expect(values).to match_array(['banana', 'eggplant']) end it "should return the number of elements removed from the set" do @namespaced.zadd('food', 0, 'orange') @namespaced.zadd('food', 0, 'banana') @namespaced.zadd('food', 0, 'eggplant') removed = @namespaced.zremrangebylex('food', '[b', '(o') expect(removed).to eq(2) values = @namespaced.zrange('food', 0, -1) expect(values).to eq(['orange']) end it "should return reverce lexicographical range for sorted set" do @namespaced.zadd('food', 0, 'orange') @namespaced.zadd('food', 0, 'banana') @namespaced.zadd('food', 0, 'eggplant') values = @namespaced.zrevrangebylex('food', '(o', '[b') expect(values).to match_array(['banana', 'eggplant']) end it "should add namespace to sort" do @namespaced.sadd('foo', 1) @namespaced.sadd('foo', 2) @namespaced.set('weight_1', 2) @namespaced.set('weight_2', 1) @namespaced.set('value_1', 'a') @namespaced.set('value_2', 'b') expect(@namespaced.sort('foo')).to eq(%w( 1 2 )) expect(@namespaced.sort('foo', :limit => [0, 1])).to eq(%w( 1 )) expect(@namespaced.sort('foo', :order => 'desc')).to eq(%w( 2 1 )) expect(@namespaced.sort('foo', :by => 'weight_*')).to eq(%w( 2 1 )) expect(@namespaced.sort('foo', :get => 'value_*')).to eq(%w( a b )) expect(@namespaced.sort('foo', :get => '#')).to eq(%w( 1 2 )) expect(@namespaced.sort('foo', :get => ['#', 'value_*'])).to eq([["1", "a"], ["2", "b"]]) @namespaced.sort('foo', :store => 'result') expect(@namespaced.lrange('result', 0, -1)).to eq(%w( 1 2 )) end it "should yield the correct list of keys" do @namespaced.set("foo", 1) @namespaced.set("bar", 2) @namespaced.set("baz", 3) expect(@namespaced.keys("*").sort).to eq(%w( bar baz foo )) expect(@namespaced.keys.sort).to eq(%w( bar baz foo )) end it "should add namepsace to multi blocks" do @namespaced.mapped_hmset "foo", {"key" => "value"} @namespaced.multi do |r| r.del "foo" r.mapped_hmset "foo", {"key1" => "value1"} end expect(@namespaced.hgetall("foo")).to eq({"key1" => "value1"}) end it "should pass through multi commands without block" do @namespaced.mapped_hmset "foo", {"key" => "value"} @namespaced.multi @namespaced.del "foo" @namespaced.mapped_hmset "foo", {"key1" => "value1"} @namespaced.exec expect(@namespaced.hgetall("foo")).to eq({"key1" => "value1"}) end it 'should return futures without attempting to remove namespaces' do @namespaced.multi do @future = @namespaced.keys('*') end expect(@future.class).to be(Redis::Future) end it "should add namespace to pipelined blocks" do @namespaced.mapped_hmset "foo", {"key" => "value"} @namespaced.pipelined do |r| r.del "foo" r.mapped_hmset "foo", {"key1" => "value1"} end expect(@namespaced.hgetall("foo")).to eq({"key1" => "value1"}) end it "should returned response array from pipelined block" do @namespaced.mset "foo", "bar", "key", "value" result = @namespaced.pipelined do |r| r.get("foo") r.get("key") end expect(result).to eq(["bar", "value"]) end it "should add namespace to strlen" do @namespaced.set("mykey", "123456") expect(@namespaced.strlen("mykey")).to eq(6) end it "should not add namespace to echo" do expect(@namespaced.echo(123)).to eq("123") end it 'should not add namespace to disconnect!' do expect(@redis).to receive(:disconnect!).with(no_args).and_call_original expect(@namespaced.disconnect!).to be nil end it "can change its namespace" do expect(@namespaced.get('foo')).to eq(nil) @namespaced.set('foo', 'chris') expect(@namespaced.get('foo')).to eq('chris') expect(@namespaced.namespace).to eq(:ns) @namespaced.namespace = :spec expect(@namespaced.namespace).to eq(:spec) expect(@namespaced.get('foo')).to eq(nil) @namespaced.set('foo', 'chris') expect(@namespaced.get('foo')).to eq('chris') end it "can accept a temporary namespace" do expect(@namespaced.namespace).to eq(:ns) expect(@namespaced.get('foo')).to eq(nil) @namespaced.namespace(:spec) do |temp_ns| expect(temp_ns.namespace).to eq(:spec) expect(temp_ns.get('foo')).to eq(nil) temp_ns.set('foo', 'jake') expect(temp_ns.get('foo')).to eq('jake') end expect(@namespaced.namespace).to eq(:ns) expect(@namespaced.get('foo')).to eq(nil) end it "should respond to :namespace=" do expect(@namespaced.respond_to?(:namespace=)).to eq(true) end it "should respond to :warning=" do expect(@namespaced.respond_to?(:warning=)).to eq(true) end it "should raise an exception when an unknown command is passed" do expect { @namespaced.unknown('foo') }.to raise_exception NoMethodError end describe '#inspect' do let(:single_level_names) { %i[first] } let(:double_level_names) { %i[first second] } let(:triple_level_names) { %i[first second third] } let(:namespace_builder) do ->(redis, *namespaces) { namespaces.reduce(redis) { |r, n| Redis::Namespace.new(n, redis: r) } } end let(:regexp_builder) do ->(*namespaces) { %r{/#{namespaces.join(':')}>\z} } end context 'when one namespace' do let(:single_namespaced) { namespace_builder.call(@redis, *single_level_names) } let(:regexp) { regexp_builder.call(*single_level_names) } it 'should have correct ending of inspect string' do expect(regexp =~ single_namespaced.inspect).not_to be(nil) end end context 'when two namespaces' do let(:double_namespaced) { namespace_builder.call(@redis, *double_level_names) } let(:regexp) { regexp_builder.call(*double_level_names) } it 'should have correct ending of inspect string' do expect(regexp =~ double_namespaced.inspect).not_to be(nil) end end context 'when three namespaces' do let(:triple_namespaced) { namespace_builder.call(@redis, *triple_level_names) } let(:regexp) { regexp_builder.call(*triple_level_names) } it 'should have correct ending of inspect string' do expect(regexp =~ triple_namespaced.inspect).not_to be(nil) end end end # Redis 2.6 RC reports its version as 2.5. if @redis_version >= Gem::Version.new("2.5.0") describe "redis 2.6 commands" do it "should namespace bitcount" do @redis.set('ns:foo', 'foobar') expect(@namespaced.bitcount('foo')).to eq 26 expect(@namespaced.bitcount('foo', 0, 0)).to eq 4 expect(@namespaced.bitcount('foo', 1, 1)).to eq 6 expect(@namespaced.bitcount('foo', 3, 5)).to eq 10 end it "should namespace bitop" do try_encoding('UTF-8') do @redis.set("ns:foo", "a") @redis.set("ns:bar", "b") @namespaced.bitop(:and, "foo&bar", "foo", "bar") @namespaced.bitop(:or, "foo|bar", "foo", "bar") @namespaced.bitop(:xor, "foo^bar", "foo", "bar") @namespaced.bitop(:not, "~foo", "foo") expect(@redis.get("ns:foo&bar")).to eq "\x60" expect(@redis.get("ns:foo|bar")).to eq "\x63" expect(@redis.get("ns:foo^bar")).to eq "\x03" expect(@redis.get("ns:~foo")).to eq "\x9E" end end it "should namespace dump and restore" do @redis.set("ns:foo", "a") v = @namespaced.dump("foo") @redis.del("ns:foo") expect(@namespaced.restore("foo", 1000, v)).to be_truthy expect(@redis.get("ns:foo")).to eq 'a' expect(@redis.ttl("ns:foo")).to satisfy {|v| (0..1).include?(v) } @redis.rpush("ns:bar", %w(b c d)) w = @namespaced.dump("bar") @redis.del("ns:bar") expect(@namespaced.restore("bar", 1000, w)).to be_truthy expect(@redis.lrange('ns:bar', 0, -1)).to eq %w(b c d) expect(@redis.ttl("ns:foo")).to satisfy {|v| (0..1).include?(v) } end it "should namespace hincrbyfloat" do @namespaced.hset('mykey', 'field', 10.50) expect(@namespaced.hincrbyfloat('mykey', 'field', 0.1)).to eq(10.6) end it "should namespace incrbyfloat" do @namespaced.set('mykey', 10.50) expect(@namespaced.incrbyfloat('mykey', 0.1)).to eq(10.6) end it "should namespace object" do @namespaced.set('foo', 1000) expect(@namespaced.object('encoding', 'foo')).to eq('int') end it "should namespace persist" do @namespaced.set('mykey', 'Hello') @namespaced.expire('mykey', 60) expect(@namespaced.persist('mykey')).to eq(true) expect(@namespaced.ttl('mykey')).to eq(-1) end it "should namespace pexpire" do @namespaced.set('mykey', 'Hello') expect(@namespaced.pexpire('mykey', 60000)).to eq(true) end it "should namespace pexpireat" do @namespaced.set('mykey', 'Hello') expect(@namespaced.pexpire('mykey', 1555555555005)).to eq(true) end it "should namespace psetex" do expect(@namespaced.psetex('mykey', 10000, 'Hello')).to eq('OK') expect(@namespaced.get('mykey')).to eq('Hello') end it "should namespace pttl" do @namespaced.set('mykey', 'Hello') @namespaced.expire('mykey', 1) expect(@namespaced.pttl('mykey')).to be >= 0 end it "should namespace eval keys passed in as array args" do expect(@namespaced. eval("return {KEYS[1], KEYS[2]}", %w[k1 k2], %w[arg1 arg2])). to eq(%w[ns:k1 ns:k2]) end it "should namespace eval keys passed in as hash args" do expect(@namespaced. eval("return {KEYS[1], KEYS[2]}", :keys => %w[k1 k2], :argv => %w[arg1 arg2])). to eq(%w[ns:k1 ns:k2]) end it "should namespace eval keys passed in as hash args unmodified" do args = { :keys => %w[k1 k2], :argv => %w[arg1 arg2] } args.freeze expect(@namespaced. eval("return {KEYS[1], KEYS[2]}", args)). to eq(%w[ns:k1 ns:k2]) end context '#evalsha' do let!(:sha) do @redis.script(:load, "return {KEYS[1], KEYS[2]}") end it "should namespace evalsha keys passed in as array args" do expect(@namespaced. evalsha(sha, %w[k1 k2], %w[arg1 arg2])). to eq(%w[ns:k1 ns:k2]) end it "should namespace evalsha keys passed in as hash args" do expect(@namespaced. evalsha(sha, :keys => %w[k1 k2], :argv => %w[arg1 arg2])). to eq(%w[ns:k1 ns:k2]) end it "should namespace evalsha keys passed in as hash args unmodified" do args = { :keys => %w[k1 k2], :argv => %w[arg1 arg2] } args.freeze expect(@namespaced. evalsha(sha, args)). to eq(%w[ns:k1 ns:k2]) end end context "in a nested namespace" do let(:nested_namespace) { Redis::Namespace.new(:nest, :redis => @namespaced) } let(:sha) { @redis.script(:load, "return {KEYS[1], KEYS[2]}") } it "should namespace eval keys passed in as hash args" do expect(nested_namespace. eval("return {KEYS[1], KEYS[2]}", :keys => %w[k1 k2], :argv => %w[arg1 arg2])). to eq(%w[ns:nest:k1 ns:nest:k2]) end it "should namespace evalsha keys passed in as hash args" do expect(nested_namespace.evalsha(sha, :keys => %w[k1 k2], :argv => %w[arg1 arg2])). to eq(%w[ns:nest:k1 ns:nest:k2]) end end end end # Redis 2.8 RC reports its version as 2.7. if @redis_version >= Gem::Version.new("2.7.105") describe "redis 2.8 commands" do context 'keyspace scan methods' do let(:keys) do %w(alpha ns:beta gamma ns:delta ns:epsilon ns:zeta:one ns:zeta:two ns:theta) end let(:namespaced_keys) do keys.map{|k| k.dup.sub!(/\Ans:/,'') }.compact.sort end before(:each) do keys.each do |key| @redis.set(key, key) end end let(:matching_namespaced_keys) do namespaced_keys.select{|k| k[/\Azeta:/] }.compact.sort end context '#scan' do context 'when :match supplied' do it 'should retrieve the proper keys' do _, result = @namespaced.scan(0, :match => 'zeta:*', :count => 1000) expect(result).to match_array(matching_namespaced_keys) end end context 'without :match supplied' do it 'should retrieve the proper keys' do _, result = @namespaced.scan(0, :count => 1000) expect(result).to match_array(namespaced_keys) end end end if Redis.current.respond_to?(:scan) context '#scan_each' do context 'when :match supplied' do context 'when given a block' do it 'should yield unnamespaced' do results = [] @namespaced.scan_each(:match => 'zeta:*', :count => 1000) {|k| results << k } expect(results).to match_array(matching_namespaced_keys) end end context 'without a block' do it 'should return an Enumerator that un-namespaces' do enum = @namespaced.scan_each(:match => 'zeta:*', :count => 1000) expect(enum.to_a).to match_array(matching_namespaced_keys) end end end context 'without :match supplied' do context 'when given a block' do it 'should yield unnamespaced' do results = [] @namespaced.scan_each(:count => 1000){ |k| results << k } expect(results).to match_array(namespaced_keys) end end context 'without a block' do it 'should return an Enumerator that un-namespaces' do enum = @namespaced.scan_each(:count => 1000) expect(enum.to_a).to match_array(namespaced_keys) end end end end if Redis.current.respond_to?(:scan_each) end context 'hash scan methods' do before(:each) do @redis.mapped_hmset('hsh', {'zeta:wrong:one' => 'WRONG', 'wrong:two' => 'WRONG'}) @redis.mapped_hmset('ns:hsh', hash) end let(:hash) do {'zeta:one' => 'OK', 'zeta:two' => 'OK', 'three' => 'OKAY'} end let(:hash_matching_subset) do # select is not consistent from 1.8.7 -> 1.9.2 :( hash.reject {|k,v| !k[/\Azeta:/] } end context '#hscan' do context 'when supplied :match' do it 'should retrieve the proper keys' do _, results = @namespaced.hscan('hsh', 0, :match => 'zeta:*') expect(results).to match_array(hash_matching_subset.to_a) end end context 'without :match supplied' do it 'should retrieve all hash keys' do _, results = @namespaced.hscan('hsh', 0) expect(results).to match_array(@redis.hgetall('ns:hsh').to_a) end end end if Redis.current.respond_to?(:hscan) context '#hscan_each' do context 'when :match supplied' do context 'when given a block' do it 'should yield the correct hash keys unchanged' do results = [] @namespaced.hscan_each('hsh', :match => 'zeta:*', :count => 1000) { |kv| results << kv} expect(results).to match_array(hash_matching_subset.to_a) end end context 'without a block' do it 'should return an Enumerator that yields the correct hash keys unchanged' do enum = @namespaced.hscan_each('hsh', :match => 'zeta:*', :count => 1000) expect(enum.to_a).to match_array(hash_matching_subset.to_a) end end end context 'without :match supplied' do context 'when given a block' do it 'should yield all hash keys unchanged' do results = [] @namespaced.hscan_each('hsh', :count => 1000){ |k| results << k } expect(results).to match_array(hash.to_a) end end context 'without a block' do it 'should return an Enumerator that yields all keys unchanged' do enum = @namespaced.hscan_each('hsh', :count => 1000) expect(enum.to_a).to match_array(hash.to_a) end end end end if Redis.current.respond_to?(:hscan_each) end context 'set scan methods' do before(:each) do set.each { |elem| @namespaced.sadd('set', elem) } @redis.sadd('set', 'WRONG') end let(:set) do %w(zeta:one zeta:two three) end let(:matching_subset) do set.select { |e| e[/\Azeta:/] } end context '#sscan' do context 'when supplied :match' do it 'should retrieve the matching set members from the proper set' do _, results = @namespaced.sscan('set', 0, :match => 'zeta:*', :count => 1000) expect(results).to match_array(matching_subset) end end context 'without :match supplied' do it 'should retrieve all set members from the proper set' do _, results = @namespaced.sscan('set', 0, :count => 1000) expect(results).to match_array(set) end end end if Redis.current.respond_to?(:sscan) context '#sscan_each' do context 'when :match supplied' do context 'when given a block' do it 'should yield the correct hset elements unchanged' do results = [] @namespaced.sscan_each('set', :match => 'zeta:*', :count => 1000) { |kv| results << kv} expect(results).to match_array(matching_subset) end end context 'without a block' do it 'should return an Enumerator that yields the correct set elements unchanged' do enum = @namespaced.sscan_each('set', :match => 'zeta:*', :count => 1000) expect(enum.to_a).to match_array(matching_subset) end end end context 'without :match supplied' do context 'when given a block' do it 'should yield all set elements unchanged' do results = [] @namespaced.sscan_each('set', :count => 1000){ |k| results << k } expect(results).to match_array(set) end end context 'without a block' do it 'should return an Enumerator that yields all set elements unchanged' do enum = @namespaced.sscan_each('set', :count => 1000) expect(enum.to_a).to match_array(set) end end end end if Redis.current.respond_to?(:sscan_each) end context 'zset scan methods' do before(:each) do hash.each {|member, score| @namespaced.zadd('zset', score, member)} @redis.zadd('zset', 123.45, 'WRONG') end let(:hash) do {'zeta:one' => 1, 'zeta:two' => 2, 'three' => 3} end let(:hash_matching_subset) do # select is not consistent from 1.8.7 -> 1.9.2 :( hash.reject {|k,v| !k[/\Azeta:/] } end context '#zscan' do context 'when supplied :match' do it 'should retrieve the matching set elements and their scores' do results = [] @namespaced.zscan_each('zset', :match => 'zeta:*', :count => 1000) { |ms| results << ms } expect(results).to match_array(hash_matching_subset.to_a) end end context 'without :match supplied' do it 'should retrieve all set elements and their scores' do results = [] @namespaced.zscan_each('zset', :count => 1000) { |ms| results << ms } expect(results).to match_array(hash.to_a) end end end if Redis.current.respond_to?(:zscan) context '#zscan_each' do context 'when :match supplied' do context 'when given a block' do it 'should yield the correct set elements and scores unchanged' do results = [] @namespaced.zscan_each('zset', :match => 'zeta:*', :count => 1000) { |ms| results << ms} expect(results).to match_array(hash_matching_subset.to_a) end end context 'without a block' do it 'should return an Enumerator that yields the correct set elements and scoresunchanged' do enum = @namespaced.zscan_each('zset', :match => 'zeta:*', :count => 1000) expect(enum.to_a).to match_array(hash_matching_subset.to_a) end end end context 'without :match supplied' do context 'when given a block' do it 'should yield all set elements and scores unchanged' do results = [] @namespaced.zscan_each('zset', :count => 1000){ |ms| results << ms } expect(results).to match_array(hash.to_a) end end context 'without a block' do it 'should return an Enumerator that yields all set elements and scores unchanged' do enum = @namespaced.zscan_each('zset', :count => 1000) expect(enum.to_a).to match_array(hash.to_a) end end end end if Redis.current.respond_to?(:zscan_each) end end end if @redis_version >= Gem::Version.new("2.8.9") it 'should namespace pfadd' do 5.times { |n| @namespaced.pfadd("pf", n) } expect(@redis.pfcount("ns:pf")).to eq(5) end it 'should namespace pfcount' do 5.times { |n| @redis.pfadd("ns:pf", n) } expect(@namespaced.pfcount("pf")).to eq(5) end it 'should namespace pfmerge' do 5.times do |n| @redis.pfadd("ns:pfa", n) @redis.pfadd("ns:pfb", n+5) end @namespaced.pfmerge("pfc", "pfa", "pfb") expect(@redis.pfcount("ns:pfc")).to eq(10) end end describe :full_namespace do it "should return the full namespace including sub namespaces" do sub_namespaced = Redis::Namespace.new(:sub1, :redis => @namespaced) sub_sub_namespaced = Redis::Namespace.new(:sub2, :redis => sub_namespaced) expect(@namespaced.full_namespace).to eql("ns") expect(sub_namespaced.full_namespace).to eql("ns:sub1") expect(sub_sub_namespaced.full_namespace).to eql("ns:sub1:sub2") end end end redis-namespace-1.8.1/spec/deprecation_spec.rb0000644000004100000410000000760314027652657021423 0ustar www-datawww-datarequire File.dirname(__FILE__) + '/spec_helper' require 'stringio' describe Redis::Namespace do # Blind passthrough of unhandled commands will be removed # in 2.0; the following tests ensure that we support them # until that point, & that we can programatically disable # them in the meantime. context 'deprecated 1.x behaviour' do let(:redis) { double(Redis) } let(:namespaced) do Redis::Namespace.new(:ns, options.merge(:redis => redis)) end let(:options) { Hash.new } subject { namespaced } its(:deprecations?) { should be false } its(:warning?) { should be true } context('with REDIS_NAMESPACE_DEPRECATIONS') do around(:each) {|e| with_env('REDIS_NAMESPACE_DEPRECATIONS'=>'1', &e) } its(:deprecations?) { should be true } end context('with REDIS_NAMESPACE_QUIET') do around(:each) {|e| with_env('REDIS_NAMESPACE_QUIET'=>'1', &e) } its(:warning?) { should be false } end before(:each) do allow(redis).to receive(:unhandled) do |*args| "unhandled(#{args.inspect})" end allow(redis).to receive(:flushdb).and_return("OK") end # This behaviour will hold true after the 2.x migration context('with deprecations enabled') do let(:options) { {:deprecations => true} } its(:deprecations?) { should be true } context('with an unhandled command') do it { is_expected.not_to respond_to :unhandled } it('raises a NoMethodError') do expect do namespaced.unhandled('foo') end.to raise_exception NoMethodError end end context('with an administrative command') do it { is_expected.not_to respond_to :flushdb } it('raises a NoMethodError') do expect do namespaced.flushdb end.to raise_exception NoMethodError end end end # This behaviour will no longer be available after the 2.x migration context('with deprecations disabled') do let(:options) { {:deprecations => false} } its(:deprecations?) { should be false } context('with an an unhandled command') do it { is_expected.to respond_to :unhandled } it 'blindly passes through' do expect(redis).to receive(:unhandled) capture_stderr do response = namespaced.unhandled('foo') expect(response).to eq 'unhandled(["foo"])' end end it 'warns with helpful output' do capture_stderr(stderr = StringIO.new) do namespaced.unhandled('bar') end warning = stderr.tap(&:rewind).read expect(warning).to_not be_empty expect(warning).to include %q(Passing 'unhandled' command to redis as is) expect(warning).to include %q(blind passthrough) expect(warning).to include __FILE__ end context('and warnings disabled') do let(:options) { super().merge(:warning => false)} it 'does not warn' do capture_stderr(stderr = StringIO.new) do namespaced.unhandled('bar') end warning = stderr.tap(&:rewind).read expect(warning).to be_empty end end end context('with an administrative command') do it { is_expected.to respond_to :flushdb } it 'processes the command' do expect(redis).to receive(:flushdb) capture_stderr { namespaced.flushdb } end it 'warns with helpful output' do capture_stderr(stderr = StringIO.new) do namespaced.flushdb end warning = stderr.tap(&:rewind).read expect(warning).to_not be_empty expect(warning).to include %q(Passing 'flushdb' command to redis as is) expect(warning).to include %q(administrative) expect(warning).to include __FILE__ end end end end end redis-namespace-1.8.1/LICENSE0000644000004100000410000000206014027652657015632 0ustar www-datawww-dataMIT License Copyright (c) 2009 Chris Wanstrath Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. redis-namespace-1.8.1/Rakefile0000644000004100000410000000041614027652657016275 0ustar www-datawww-datarequire 'rubygems' require "rspec/core/rake_task" require "bundler/gem_tasks" RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = 'spec/*_spec.rb' spec.rspec_opts = ['--backtrace'] spec.ruby_opts = ['-w'] end task :default => :spec task :test => :spec redis-namespace-1.8.1/lib/0000755000004100000410000000000014027652657015375 5ustar www-datawww-dataredis-namespace-1.8.1/lib/redis/0000755000004100000410000000000014027652657016503 5ustar www-datawww-dataredis-namespace-1.8.1/lib/redis/namespace.rb0000644000004100000410000004536514027652657021001 0ustar www-datawww-datarequire 'redis' require 'redis/namespace/version' class Redis class Namespace # 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 NAMESPACED_COMMANDS = { "append" => [ :first ], "bitcount" => [ :first ], "bitop" => [ :exclude_first ], "bitpos" => [ :first ], "blpop" => [ :exclude_last, :first ], "brpop" => [ :exclude_last, :first ], "brpoplpush" => [ :exclude_last ], "bzpopmin" => [ :first ], "bzpopmax" => [ :first ], "debug" => [ :exclude_first ], "decr" => [ :first ], "decrby" => [ :first ], "del" => [ :all ], "dump" => [ :first ], "exists" => [ :all ], "exists?" => [ :all ], "expire" => [ :first ], "expireat" => [ :first ], "eval" => [ :eval_style ], "evalsha" => [ :eval_style ], "get" => [ :first ], "getbit" => [ :first ], "getrange" => [ :first ], "getset" => [ :first ], "hset" => [ :first ], "hsetnx" => [ :first ], "hget" => [ :first ], "hincrby" => [ :first ], "hincrbyfloat" => [ :first ], "hmget" => [ :first ], "hmset" => [ :first ], "hdel" => [ :first ], "hexists" => [ :first ], "hlen" => [ :first ], "hkeys" => [ :first ], "hscan" => [ :first ], "hscan_each" => [ :first ], "hvals" => [ :first ], "hgetall" => [ :first ], "incr" => [ :first ], "incrby" => [ :first ], "incrbyfloat" => [ :first ], "keys" => [ :first, :all ], "lindex" => [ :first ], "linsert" => [ :first ], "llen" => [ :first ], "lpop" => [ :first ], "lpush" => [ :first ], "lpushx" => [ :first ], "lrange" => [ :first ], "lrem" => [ :first ], "lset" => [ :first ], "ltrim" => [ :first ], "mapped_hmset" => [ :first ], "mapped_hmget" => [ :first ], "mapped_mget" => [ :all, :all ], "mapped_mset" => [ :all ], "mapped_msetnx" => [ :all ], "mget" => [ :all ], "monitor" => [ :monitor ], "move" => [ :first ], "mset" => [ :alternate ], "msetnx" => [ :alternate ], "object" => [ :exclude_first ], "persist" => [ :first ], "pexpire" => [ :first ], "pexpireat" => [ :first ], "pfadd" => [ :first ], "pfcount" => [ :all ], "pfmerge" => [ :all ], "psetex" => [ :first ], "psubscribe" => [ :all ], "pttl" => [ :first ], "publish" => [ :first ], "punsubscribe" => [ :all ], "rename" => [ :all ], "renamenx" => [ :all ], "restore" => [ :first ], "rpop" => [ :first ], "rpoplpush" => [ :all ], "rpush" => [ :first ], "rpushx" => [ :first ], "sadd" => [ :first ], "scard" => [ :first ], "scan" => [ :scan_style, :second ], "scan_each" => [ :scan_style, :all ], "sdiff" => [ :all ], "sdiffstore" => [ :all ], "set" => [ :first ], "setbit" => [ :first ], "setex" => [ :first ], "setnx" => [ :first ], "setrange" => [ :first ], "sinter" => [ :all ], "sinterstore" => [ :all ], "sismember" => [ :first ], "smembers" => [ :first ], "smove" => [ :exclude_last ], "sort" => [ :sort ], "spop" => [ :first ], "srandmember" => [ :first ], "srem" => [ :first ], "sscan" => [ :first ], "sscan_each" => [ :first ], "strlen" => [ :first ], "subscribe" => [ :all ], "sunion" => [ :all ], "sunionstore" => [ :all ], "ttl" => [ :first ], "type" => [ :first ], "unlink" => [ :all ], "unsubscribe" => [ :all ], "zadd" => [ :first ], "zcard" => [ :first ], "zcount" => [ :first ], "zincrby" => [ :first ], "zinterstore" => [ :exclude_options ], "zpopmin" => [ :first ], "zpopmax" => [ :first ], "zrange" => [ :first ], "zrangebyscore" => [ :first ], "zrangebylex" => [ :first ], "zrank" => [ :first ], "zrem" => [ :first ], "zremrangebyrank" => [ :first ], "zremrangebyscore" => [ :first ], "zremrangebylex" => [ :first ], "zrevrange" => [ :first ], "zrevrangebyscore" => [ :first ], "zrevrangebylex" => [ :first ], "zrevrank" => [ :first ], "zscan" => [ :first ], "zscan_each" => [ :first ], "zscore" => [ :first ], "zunionstore" => [ :exclude_options ] } TRANSACTION_COMMANDS = { "discard" => [], "exec" => [], "multi" => [], "unwatch" => [ :all ], "watch" => [ :all ], } HELPER_COMMANDS = { "auth" => [], "disconnect!" => [], "echo" => [], "ping" => [], "time" => [], } ADMINISTRATIVE_COMMANDS = { "bgrewriteaof" => [], "bgsave" => [], "config" => [], "dbsize" => [], "flushall" => [], "flushdb" => [], "info" => [], "lastsave" => [], "quit" => [], "randomkey" => [], "save" => [], "script" => [], "select" => [], "shutdown" => [], "slaveof" => [], } DEPRECATED_COMMANDS = [ ADMINISTRATIVE_COMMANDS ].compact.reduce(:merge) COMMANDS = [ NAMESPACED_COMMANDS, TRANSACTION_COMMANDS, HELPER_COMMANDS, ADMINISTRATIVE_COMMANDS, ].compact.reduce(:merge) # Support 1.8.7 by providing a namespaced reference to Enumerable::Enumerator Enumerator = Enumerable::Enumerator unless defined?(::Enumerator) attr_writer :namespace attr_reader :redis attr_accessor :warning 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 def deprecations? @deprecations end def warning? @warning end 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 def _client @has_new_client_method ? @redis._client : @redis.client # for redis-4.0.0 end # Ruby defines a now deprecated type method so we need to override it here # since it will never hit method_missing def type(key) call_with_namespace(:type, key) end alias_method :self_respond_to?, :respond_to? # emulate Ruby 1.9+ and keep respond_to_missing? logic together. 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 def keys(query = nil) call_with_namespace(:keys, query || '*') end def multi(&block) if block_given? namespaced_block(:multi, &block) else call_with_namespace(:multi) end end def pipelined(&block) namespaced_block(:pipelined, &block) end def namespace(desired_namespace = nil) if desired_namespace yield Redis::Namespace.new(desired_namespace, :redis => @redis) end @namespace end def full_namespace redis.is_a?(Namespace) ? "#{redis.full_namespace}:#{namespace}" : namespace.to_s end def connection @redis.connection.tap { |info| info[:namespace] = @namespace } end def exec call_with_namespace(:exec) end def eval(*args) call_with_namespace(:eval, *args) end ruby2_keywords(:eval) if respond_to?(:ruby2_keywords, true) ADMINISTRATIVE_COMMANDS.keys.each do |command| define_method(command) do |*args, &block| raise NoMethodError if deprecations? if warning? warn("Passing '#{command}' command to redis as is; " + "administrative commands cannot be effectively namespaced " + "and should be called on the redis connection directly; " + "passthrough has been deprecated and will be removed in " + "redis-namespace 2.0 (at #{call_site})" ) end call_with_namespace(command, *args, &block) end ruby2_keywords(command) if respond_to?(:ruby2_keywords, true) end COMMANDS.keys.each do |command| next if ADMINISTRATIVE_COMMANDS.include?(command) next if method_defined?(command) define_method(command) do |*args, &block| call_with_namespace(command, *args, &block) end ruby2_keywords(command) if respond_to?(:ruby2_keywords, true) end 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 ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) def inspect "<#{self.class.name} v#{VERSION} with client v#{Redis::VERSION} "\ "for #{@redis.id}/#{full_namespace}>" end 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 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 ruby2_keywords(:call_with_namespace) if respond_to?(:ruby2_keywords, true) private if Hash.respond_to?(:ruby2_keywords_hash) def ruby2_keywords_hash(kwargs) Hash.ruby2_keywords_hash(kwargs) end else def ruby2_keywords_hash(kwargs) kwargs end end # Avoid modifying the caller's (pass-by-reference) arguments. 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 def call_site caller.reject { |l| l.start_with?(__FILE__) }.first end def namespaced_block(command, &block) redis.send(command) do |r| begin original, @redis = @redis, r yield self ensure @redis = original end end end 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 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 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 end end redis-namespace-1.8.1/lib/redis/namespace/0000755000004100000410000000000014027652657020437 5ustar www-datawww-dataredis-namespace-1.8.1/lib/redis/namespace/version.rb0000644000004100000410000000012114027652657022443 0ustar www-datawww-data# encoding: utf-8 class Redis class Namespace VERSION = '1.8.1' end end redis-namespace-1.8.1/lib/redis-namespace.rb0000644000004100000410000000003214027652657020755 0ustar www-datawww-datarequire 'redis/namespace' redis-namespace-1.8.1/redis-namespace.gemspec0000644000004100000410000000517514027652657021244 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: redis-namespace 1.8.1 ruby lib Gem::Specification.new do |s| s.name = "redis-namespace".freeze s.version = "1.8.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/resque/redis-namespace/issues", "changelog_uri" => "https://github.com/resque/redis-namespace/blob/master/CHANGELOG.md", "documentation_uri" => "https://www.rubydoc.info/gems/redis-namespace/1.8.1", "source_code_uri" => "https://github.com/resque/redis-namespace/tree/v1.8.1" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Chris Wanstrath".freeze, "Terence Lee".freeze, "Steve Klabnik".freeze, "Ryan Biesemeyer".freeze] s.date = "2021-02-04" s.description = "Adds a Redis::Namespace class which can be used to namespace calls\nto Redis. This is useful when using a single instance of Redis with\nmultiple, different applications.\n".freeze s.email = ["chris@ozmm.org".freeze, "hone02@gmail.com".freeze, "steve@steveklabnik.com".freeze, "me@yaauie.com".freeze] s.files = ["LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "lib/redis-namespace.rb".freeze, "lib/redis/namespace.rb".freeze, "lib/redis/namespace/version.rb".freeze, "spec/deprecation_spec.rb".freeze, "spec/redis_spec.rb".freeze, "spec/spec_helper.rb".freeze] s.homepage = "https://github.com/resque/redis-namespace".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.4".freeze) s.rubygems_version = "2.7.6.2".freeze s.summary = "Namespaces Redis commands.".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, [">= 0"]) s.add_runtime_dependency(%q.freeze, [">= 3.0.4"]) s.add_development_dependency(%q.freeze, ["~> 3.7"]) s.add_development_dependency(%q.freeze, [">= 0"]) else s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 3.0.4"]) s.add_dependency(%q.freeze, ["~> 3.7"]) s.add_dependency(%q.freeze, [">= 0"]) end else s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, [">= 3.0.4"]) s.add_dependency(%q.freeze, ["~> 3.7"]) s.add_dependency(%q.freeze, [">= 0"]) end end