redis-activesupport-5.0.4/0000755000004100000410000000000013205552634015571 5ustar www-datawww-dataredis-activesupport-5.0.4/Rakefile0000644000004100000410000000014713205552634017240 0ustar www-datawww-datarequire 'bundler/setup' require 'rake' require 'bundler/gem_tasks' require 'redis-store/testing/tasks' redis-activesupport-5.0.4/Gemfile0000644000004100000410000000006213205552634017062 0ustar www-datawww-datasource 'https://rubygems.org' gemspec gem 'i18n' redis-activesupport-5.0.4/MIT-LICENSE0000644000004100000410000000204513205552634017226 0ustar www-datawww-dataCopyright (c) 2009 - 2011 Luca Guidi 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-activesupport-5.0.4/redis-activesupport.gemspec0000644000004100000410000000235413205552634023156 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path('../lib', __FILE__) require 'redis/active_support/version' Gem::Specification.new do |s| s.name = 'redis-activesupport' s.version = Redis::ActiveSupport::VERSION s.authors = ['Luca Guidi', 'Ryan Bigg'] s.email = ['me@lucaguidi.com', 'me@ryanbigg.com'] s.homepage = 'http://redis-store.org/redis-activesupport' s.summary = %q{Redis store for ActiveSupport} s.description = %q{Redis store for ActiveSupport} s.license = 'MIT' s.rubyforge_project = 'redis-activesupport' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ['lib'] s.add_runtime_dependency "redis-store", '>= 1.3', '< 2' s.add_runtime_dependency 'activesupport', '>= 3', '< 6' s.add_development_dependency 'rake', '~> 10' s.add_development_dependency 'bundler', '~> 1.3' s.add_development_dependency 'mocha', '~> 0.14.0' s.add_development_dependency 'minitest', '>= 4.2', '< 6' s.add_development_dependency 'connection_pool', '~> 2.2.0' s.add_development_dependency 'redis-store-testing' end redis-activesupport-5.0.4/.travis.yml0000644000004100000410000000072413205552634017705 0ustar www-datawww-datalanguage: ruby script: 'bundle exec rake' gemfile: - gemfiles/Gemfile.activesupport-3.x - gemfiles/Gemfile.activesupport-4.x - gemfiles/Gemfile.activesupport-5.x rvm: - 2.0 - 2.1 - 2.2 - 2.3 - 2.4 - ruby-head - jruby-head services: - redis-server matrix: allow_failures: - rvm: jruby-head - rvm: ruby-head - rvm: 2.0 gemfile: gemfiles/Gemfile.activesupport-5.x - rvm: 2.1 gemfile: gemfiles/Gemfile.activesupport-5.x redis-activesupport-5.0.4/lib/0000755000004100000410000000000013205552634016337 5ustar www-datawww-dataredis-activesupport-5.0.4/lib/redis-activesupport.rb0000644000004100000410000000013213205552634022674 0ustar www-datawww-datarequire 'redis-store' require 'active_support' require 'active_support/cache/redis_store' redis-activesupport-5.0.4/lib/active_support/0000755000004100000410000000000013205552634021406 5ustar www-datawww-dataredis-activesupport-5.0.4/lib/active_support/cache/0000755000004100000410000000000013205552634022451 5ustar www-datawww-dataredis-activesupport-5.0.4/lib/active_support/cache/redis_store.rb0000644000004100000410000002327213205552634025326 0ustar www-datawww-data# encoding: UTF-8 require 'redis-store' module ActiveSupport module Cache class RedisStore < Store ERRORS_TO_RESCUE = [ Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Redis::CannotConnectError, Redis::ConnectionError ].freeze attr_reader :data # Instantiate the store. # # Example: # RedisStore.new # # => host: localhost, port: 6379, db: 0 # # RedisStore.new "example.com" # # => host: example.com, port: 6379, db: 0 # # RedisStore.new "example.com:23682" # # => host: example.com, port: 23682, db: 0 # # RedisStore.new "example.com:23682/1" # # => host: example.com, port: 23682, db: 1 # # RedisStore.new "example.com:23682/1/theplaylist" # # => host: example.com, port: 23682, db: 1, namespace: theplaylist # # RedisStore.new "localhost:6379/0", "localhost:6380/0" # # => instantiate a cluster # # RedisStore.new "localhost:6379/0", "localhost:6380/0", pool_size: 5, pool_timeout: 10 # # => use a ConnectionPool # # RedisStore.new "localhost:6379/0", "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) def initialize(*addresses) @options = addresses.dup.extract_options! addresses = addresses.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) } else ::Redis::Store::Factory.create(*addresses) end super(@options) end def write(name, value, options = nil) options = merged_options(options) instrument(:write, name, options) do |payload| entry = options[:raw].present? ? value : Entry.new(value, options) 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 write_entry(normalize_key(name, options), entry, options) end end # 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*" def delete_matched(matcher, options = nil) options = merged_options(options) instrument(:delete_matched, matcher.inspect) do matcher = key_matcher(matcher, options) begin with do |store| !(keys = store.keys(matcher)).empty? && store.del(*keys) end rescue *ERRORS_TO_RESCUE raise if raise_errors? false end end end # 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 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| 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 rescue *ERRORS_TO_RESCUE raise if raise_errors? {} end 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 begin with do |c| c.multi do need_writes.each do |name, value| write(name, value, options) end end end rescue *ERRORS_TO_RESCUE raise if raise_errors? end fetched end # 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 # => # # # 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" def increment(key, amount = 1, options = {}) options = merged_options(options) instrument(:increment, key, :amount => amount) do with{|c| c.incrby normalize_key(key, options), amount} end end # 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 # => # # # 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" def decrement(key, amount = 1, options = {}) options = merged_options(options) instrument(:decrement, key, :amount => amount) do with{|c| c.decrby normalize_key(key, options), amount} end end def expire(key, ttl) options = merged_options(nil) with { |c| c.expire normalize_key(key, options), ttl } end # Clear all the data from the store. def clear instrument(:clear, nil, nil) do with(&:flushdb) end end # fixed problem with invalid exists? method # https://github.com/rails/rails/commit/cad2c8f5791d5bd4af0f240d96e00bae76eabd2f def exist?(name, options = nil) res = super(name, options) res || false end def stats with(&:info) end def with(&block) if defined?(@pooled) && @pooled @data.with(&block) else block.call(@data) end end def reconnect @data.reconnect if @data.respond_to?(:reconnect) end protected def write_entry(key, entry, options) method = options && options[:unless_exist] ? :setnx : :set with { |client| client.send method, key, entry, options } rescue *ERRORS_TO_RESCUE raise if raise_errors? false end def read_entry(key, options) entry = with { |c| c.get key, options } if entry entry.is_a?(ActiveSupport::Cache::Entry) ? entry : ActiveSupport::Cache::Entry.new(entry) end rescue *ERRORS_TO_RESCUE raise if raise_errors? nil end ## # Implement the ActiveSupport::Cache#delete_entry # # It's really needed and use # def delete_entry(key, options) with { |c| c.del key } rescue *ERRORS_TO_RESCUE raise if raise_errors? false end def raise_errors? !!@options[:raise_errors] end # 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. 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 private if ActiveSupport::VERSION::MAJOR < 5 def normalize_key(*args) namespaced_key(*args) end end end end end redis-activesupport-5.0.4/lib/redis/0000755000004100000410000000000013205552634017445 5ustar www-datawww-dataredis-activesupport-5.0.4/lib/redis/active_support/0000755000004100000410000000000013205552634022514 5ustar www-datawww-dataredis-activesupport-5.0.4/lib/redis/active_support/version.rb0000644000004100000410000000010313205552634024520 0ustar www-datawww-dataclass Redis module ActiveSupport VERSION = '5.0.4' end end redis-activesupport-5.0.4/gemfiles/0000755000004100000410000000000013205552634017364 5ustar www-datawww-dataredis-activesupport-5.0.4/gemfiles/Gemfile.activesupport-5.x0000644000004100000410000000045413205552634024201 0ustar www-datawww-datasource "http://rubygems.org" gem 'redis-store', '~> 1.1.0' gem 'activesupport', '>= 5.0.0.beta1', '< 5.1' group :development do gem 'rake', '~> 10' gem 'bundler', '~> 1.3' gem 'mocha', '~> 0.14.0' gem 'minitest', '~> 5.1' gem 'connection_pool', '~> 1.2.0' gem 'redis-store-testing' end redis-activesupport-5.0.4/gemfiles/Gemfile.activesupport-4.x0000644000004100000410000000043113205552634024173 0ustar www-datawww-datasource "http://rubygems.org" gem 'redis-store', '~> 1.1.0' gem 'activesupport', '~> 4' group :development do gem 'rake', '~> 10' gem 'bundler', '~> 1.3' gem 'mocha', '~> 0.14.0' gem 'minitest', '~> 4.2' gem 'connection_pool', '~> 1.2.0' gem 'redis-store-testing' end redis-activesupport-5.0.4/gemfiles/Gemfile.activesupport-3.x0000644000004100000410000000043113205552634024172 0ustar www-datawww-datasource "http://rubygems.org" gem 'redis-store', '~> 1.1.0' gem 'activesupport', '~> 3' group :development do gem 'rake', '~> 10' gem 'bundler', '~> 1.3' gem 'mocha', '~> 0.14.0' gem 'minitest', '~> 4.2' gem 'connection_pool', '~> 1.2.0' gem 'redis-store-testing' end redis-activesupport-5.0.4/test/0000755000004100000410000000000013205552634016550 5ustar www-datawww-dataredis-activesupport-5.0.4/test/test_helper.rb0000644000004100000410000000032613205552634021414 0ustar www-datawww-datarequire 'bundler/setup' require 'minitest/autorun' require 'mocha/setup' require 'active_support' require 'active_support/cache/redis_store' puts "Testing against ActiveSupport v.#{ActiveSupport::VERSION::STRING}"redis-activesupport-5.0.4/test/active_support/0000755000004100000410000000000013205552634021617 5ustar www-datawww-dataredis-activesupport-5.0.4/test/active_support/cache/0000755000004100000410000000000013205552634022662 5ustar www-datawww-dataredis-activesupport-5.0.4/test/active_support/cache/redis_store_test.rb0000644000004100000410000005652413205552634026604 0ustar www-datawww-datarequire 'test_helper' require 'ostruct' require 'connection_pool' describe ActiveSupport::Cache::RedisStore do def setup @store = ActiveSupport::Cache::RedisStore.new @dstore = ActiveSupport::Cache::RedisStore.new "redis://127.0.0.1:6379/5", "redis://127.0.0.1:6379/6" @pool_store = ActiveSupport::Cache::RedisStore.new("redis://127.0.0.1:6379/2", pool_size: 5, pool_timeout: 10) @external_pool_store = ActiveSupport::Cache::RedisStore.new(pool: ::ConnectionPool.new(size: 1, timeout: 1) { ::Redis::Store::Factory.create("redis://127.0.0.1:6379/3") }) @pool_store.data.class.must_equal ::ConnectionPool @pool_store.data.instance_variable_get(:@size).must_equal 5 @external_pool_store.data.class.must_equal ::ConnectionPool @external_pool_store.data.instance_variable_get(:@size).must_equal 1 @rabbit = OpenStruct.new :name => "bunny" @white_rabbit = OpenStruct.new :color => "white" with_store_management do |store| store.write "rabbit", @rabbit store.delete "counter" store.delete "rub-a-dub" store.delete({hkey: 'test'}) end end it "connects using an hash of options" do address = { host: '127.0.0.1', port: '6380', db: '1' } store = ActiveSupport::Cache::RedisStore.new(address.merge(pool_size: 5, pool_timeout: 10)) redis = Redis.new(url: "redis://127.0.0.1:6380/1") redis.flushall store.data.class.must_equal(::ConnectionPool) store.data.instance_variable_get(:@size).must_equal(5) store.data.instance_variable_get(:@timeout).must_equal(10) store.write("rabbit", 0) redis.exists("rabbit").must_equal(true) end it "connects using an string of options" do address = "redis://127.0.0.1:6380/1" store = ActiveSupport::Cache::RedisStore.new(address, pool_size: 5, pool_timeout: 10) redis = Redis.new(url: address) redis.flushall store.data.class.must_equal(::ConnectionPool) store.data.instance_variable_get(:@size).must_equal(5) store.data.instance_variable_get(:@timeout).must_equal(10) store.write("rabbit", 0) redis.exists("rabbit").must_equal(true) end it "connects using the passed hash of options" do address = { host: '127.0.0.1', port: '6380', db: '1' }.merge(pool_size: 5, pool_timeout: 10) store = ActiveSupport::Cache::RedisStore.new(address) redis = Redis.new(url: "redis://127.0.0.1:6380/1") redis.flushall address[:db] = '0' # Should not use this db store.data.class.must_equal(::ConnectionPool) store.write("rabbit", 0) redis.exists("rabbit").must_equal(true) end it "raises an error if :pool isn't a pool" do assert_raises(RuntimeError, 'pool must be an instance of ConnectionPool') do ActiveSupport::Cache::RedisStore.new(pool: 'poolio') end end it "namespaces all operations" do address = "redis://127.0.0.1:6380/1/cache-namespace" store = ActiveSupport::Cache::RedisStore.new(address) redis = Redis.new(url: address) store.write("white-rabbit", 0) redis.exists('cache-namespace:white-rabbit').must_equal(true) end it "creates a normal store when given no addresses" do underlying_store = instantiate_store underlying_store.must_be_instance_of(::Redis::Store) end it "creates a normal store when given options only" do underlying_store = instantiate_store(:expires_in => 1.second) underlying_store.must_be_instance_of(::Redis::Store) end it "creates a normal store when given a single address" do underlying_store = instantiate_store("redis://127.0.0.1:6380/1") underlying_store.must_be_instance_of(::Redis::Store) end it "creates a normal store when given a single address and options" do underlying_store = instantiate_store("redis://127.0.0.1:6380/1", { :expires_in => 1.second}) underlying_store.must_be_instance_of(::Redis::Store) end it "creates a distributed store when given multiple addresses" do underlying_store = instantiate_store("redis://127.0.0.1:6380/1", "redis://127.0.0.1:6381/1") underlying_store.must_be_instance_of(::Redis::DistributedStore) end it "creates a distributed store when given multiple address and options" do underlying_store = instantiate_store("redis://127.0.0.1:6380/1", "redis://127.0.0.1:6381/1", :expires_in => 1.second) underlying_store.must_be_instance_of(::Redis::DistributedStore) end it "reads the data" do with_store_management do |store| store.read("rabbit").must_equal(@rabbit) end end it "writes the data" do with_store_management do |store| store.write "rabbit", @white_rabbit store.read("rabbit").must_equal(@white_rabbit) end end it "writes the data with specified namespace" do with_store_management do |store| store.write "rabbit", @white_rabbit, namespace:'namespaced' store.read("namespaced:rabbit").must_equal(@white_rabbit) end end it "writes the data with expiration time" do with_store_management do |store| store.write "rabbit", @white_rabbit, :expires_in => 1.second store.read("rabbit").must_equal(@white_rabbit) sleep 2 store.read("rabbit").must_be_nil end end it "respects expiration time in seconds" do with_store_management do |store| store.write "rabbit", @white_rabbit store.read("rabbit").must_equal(@white_rabbit) store.expire "rabbit", 1.second sleep 2 store.read("rabbit").must_be_nil end end it "respects expiration time in seconds for object key" do with_store_management do |store| store.write({ hkey: 'test' }, @white_rabbit) store.read({ hkey: 'test' }).must_equal(@white_rabbit) store.expire({ hkey: 'test' }, 1.second) sleep 2 store.read({ hkey: 'test' }).must_be_nil end end it "does't write data if :unless_exist option is true" do with_store_management do |store| store.write "rabbit", @white_rabbit, :unless_exist => true store.read("rabbit").must_equal(@rabbit) end end if RUBY_VERSION.match(/1\.9/) it "reads raw data" do with_store_management do |store| result = store.read("rabbit", :raw => true) result.must_include("ActiveSupport::Cache::Entry") result.must_include("\x0FOpenStruct{\x06:\tnameI\"\nbunny\x06:\x06EF") end end else it "reads raw data" do with_store_management do |store| result = store.read("rabbit", :raw => true) result.must_include("ActiveSupport::Cache::Entry") result.must_include("\017OpenStruct{\006:\tname") end end end it "writes raw data" do with_store_management do |store| store.write "rabbit", @white_rabbit, :raw => true store.read("rabbit", :raw => true).must_equal(%(#)) end end it "deletes data" do with_store_management do |store| store.delete "rabbit" store.read("rabbit").must_be_nil end end it "deletes namespaced data" do with_store_management do |store| store.write "rabbit", @white_rabbit, namespace:'namespaced' store.delete "rabbit", namespace:'namespaced' store.read("namespaced:rabbit").must_be_nil end end it "deletes matched data" do with_store_management do |store| store.write "rabbit2", @white_rabbit store.write "rub-a-dub", "Flora de Cana" store.delete_matched "rabb*" store.read("rabbit").must_be_nil store.read("rabbit2").must_be_nil store.exist?("rub-a-dub").must_equal(true) end end it 'deletes matched data with a regexp' do with_store_management do |store| store.write "rabbit2", @white_rabbit store.write "rub-a-dub", "Flora de Cana" store.delete_matched(/rabb*/) store.read("rabbit").must_be_nil store.read("rabbit2").must_be_nil store.exist?("rub-a-dub").must_equal(true) end end it "verifies existence of an object in the store" do with_store_management do |store| store.exist?("rabbit").must_equal(true) store.exist?("rab-a-dub").must_equal(false) end end it "increments a key" do with_store_management do |store| 3.times { store.increment "counter" } store.read("counter", :raw => true).to_i.must_equal(3) end end it "decrements a key" do with_store_management do |store| 3.times { store.increment "counter" } 2.times { store.decrement "counter" } store.read("counter", :raw => true).to_i.must_equal(1) end end it "increments an object key" do with_store_management do |store| 3.times { store.increment({ hkey: 'test' }) } store.read({ hkey: 'test' }, :raw => true).to_i.must_equal(3) end end it "decrements an object key" do with_store_management do |store| 3.times { store.increment({ hkey: 'test' }) } 2.times { store.decrement({ hkey: 'test' }) } store.read({hkey: 'test'}, :raw => true).to_i.must_equal(1) end end it "increments a raw key" do with_store_management do |store| assert store.write("raw-counter", 1, :raw => true) store.increment("raw-counter", 2) store.read("raw-counter", :raw => true).to_i.must_equal(3) end end it "increments a key with options argument" do with_store_management do |store| assert store.write("raw-counter", 1, :raw => true) store.increment("raw-counter", 2, nil) store.read("raw-counter", :raw => true).to_i.must_equal(3) end end it "decrements a raw key" do with_store_management do |store| assert store.write("raw-counter", 3, :raw => true) store.decrement("raw-counter", 2) store.read("raw-counter", :raw => true).to_i.must_equal(1) end end it "increments a key by given value" do with_store_management do |store| store.increment "counter", 3 store.read("counter", :raw => true).to_i.must_equal(3) end end it "decrements a key by given value" do with_store_management do |store| 3.times { store.increment "counter" } store.decrement "counter", 2 store.read("counter", :raw => true).to_i.must_equal(1) end end it "decrements a key with an options argument" do with_store_management do |store| 3.times { store.increment "counter" } store.decrement "counter", 2, nil store.read("counter", :raw => true).to_i.must_equal(1) end end it "clears the store" do with_store_management do |store| store.clear store.with { |client| client.keys("*") }.flatten.must_be_empty end end it "provides store stats" do with_store_management do |store| store.stats.wont_be_empty end end it "fetches data" do with_store_management do |store| store.fetch("rabbit").must_equal(@rabbit) store.fetch("rub-a-dub").must_be_nil store.fetch("rub-a-dub") { "Flora de Cana" } store.fetch("rub-a-dub").must_equal("Flora de Cana") end end it "fetches data with expiration time" do with_store_management do |store| store.fetch("rabbit", :force => true) {} # force cache miss store.fetch("rabbit", :force => true, :expires_in => 1.second) { @white_rabbit } store.fetch("rabbit").must_equal(@white_rabbit) sleep 2 store.fetch("rabbit").must_be_nil end end it "fetches namespaced data" do with_store_management do |store| store.delete("rabbit", namespace:'namespaced') store.fetch("rabbit", namespace:'namespaced'){@rabbit}.must_equal(@rabbit) store.read("rabbit", namespace:'namespaced').must_equal(@rabbit) end end describe "race_condition_ttl on fetch" do it "persist entry for longer than given ttl" do options = { force: true, expires_in: 1.second, race_condition_ttl: 2.seconds } @store.fetch("rabbit", options) { @rabbit } sleep 1.1 @store.delete("rabbit").must_equal(1) end it "limits stampede time to read-write duration" do first_rabbit = second_rabbit = nil options = { force: true, expires_in: 1.second, race_condition_ttl: 2.seconds } @store.fetch("rabbit", options) { @rabbit } sleep 1 th1 = Thread.new do first_rabbit = @store.fetch("rabbit", race_condition_ttl: 2) do sleep 1 @white_rabbit end end sleep 0.1 th2 = Thread.new do second_rabbit = @store.fetch("rabbit") { @white_rabbit } end th1.join th2.join first_rabbit.must_equal(@white_rabbit) second_rabbit.must_equal(@rabbit) @store.fetch("rabbit").must_equal(@white_rabbit) end end it "reads multiple keys" do @store.write "irish whisky", "Jameson" result = @store.read_multi "rabbit", "irish whisky" result['rabbit'].must_equal(@rabbit) result['irish whisky'].must_equal("Jameson") end it "reads multiple keys and returns only the matched ones" do @store.delete 'irish whisky' result = @store.read_multi "rabbit", "irish whisky" result.wont_include('irish whisky') result.must_include('rabbit') end it "reads multiple namespaced keys" do @store.write "rub-a-dub", "Flora de Cana", namespace:'namespaced' @store.write "irish whisky", "Jameson", namespace:'namespaced' result = @store.read_multi "rub-a-dub", "irish whisky", namespace:'namespaced' result['rub-a-dub'].must_equal("Flora de Cana") result['irish whisky'].must_equal("Jameson") end it "read_multi return an empty {} when given an empty array" do result = @store.read_multi(*[]) result.must_equal({}) end it "read_multi return an empty {} when given an empty array with option" do result = @store.read_multi(*[], option: true) result.must_equal({}) end it "read_multi returns values with raw option" do @store.write "raw-value-a", "A", raw: true @store.write "raw-value-b", "B", raw: true result = @store.read_multi("raw-value-a", "raw-value-b", :raw => true) result.must_equal({ "raw-value-a" => "A", "raw-value-b" => "B" }) end describe "fetch_multi" do it "reads existing keys and fills in anything missing" do @store.write "bourbon", "makers" result = @store.fetch_multi("bourbon", "rye") do |key| "#{key}-was-missing" end result.must_equal({ "bourbon" => "makers", "rye" => "rye-was-missing" }) @store.read("rye").must_equal("rye-was-missing") end it "fetch command within fetch_multi block" do @store.delete 'rye' @store.write "bourbon", "makers" result = @store.fetch_multi("bourbon", "rye") do |key| @store.fetch "inner-#{key}" do "#{key}-was-missing" end end result.must_equal({ "bourbon" => "makers", "rye" => "rye-was-missing" }) @store.read("rye").must_equal("rye-was-missing") @store.read("inner-rye").must_equal("rye-was-missing") end it "return an empty {} when given an empty array" do result = @store.fetch_multi(*[]) { 1 } result.must_equal({}) end it "return an empty {} when given an empty array with option" do result = @store.read_multi(*[], option: true) result.must_equal({}) end end describe "fetch_multi namespaced keys" do it "reads existing keys and fills in anything missing" do @store.write "bourbon", "makers", namespace: 'namespaced' result = @store.fetch_multi("bourbon", "rye", namespace: 'namespaced') do |key| "#{key}-was-missing" end result.must_equal({ "bourbon" => "makers", "rye" => "rye-was-missing" }) @store.read("namespaced:rye").must_equal("rye-was-missing") end it "fetch command within fetch_multi block" do @store.delete 'namespaced:rye' @store.write "bourbon", "makers", namespace: 'namespaced' result = @store.fetch_multi("bourbon", "rye", namespace: 'namespaced') do |key| @store.fetch "namespaced:inner-#{key}" do "#{key}-was-missing" end end result.must_equal({ "bourbon" => "makers", "rye" => "rye-was-missing" }) @store.read("namespaced:rye").must_equal("rye-was-missing") @store.read("namespaced:inner-rye").must_equal("rye-was-missing") end end describe "fetch_multi nested keys" do it "reads existing keys and fills in anything missing" do @store.write ["bourbon", "bourbon-extended"], "makers" bourbon_key = ["bourbon", "bourbon-extended"] rye_key = ["rye", "rye-extended"] result = @store.fetch_multi(bourbon_key, rye_key) do |key| "#{key}-was-missing" end result.must_equal({ bourbon_key => "makers", rye_key => "#{rye_key}-was-missing" }) @store.read(rye_key).must_equal("#{rye_key}-was-missing") end end describe "notifications" do it "notifies on #fetch" do with_notifications do @store.fetch("radiohead") { "House Of Cards" } end read, generate, write = @events if ActiveSupport::VERSION::MAJOR < 5 read_payload = { :key => 'radiohead', :super_operation => :fetch } else read_payload = { :key => 'radiohead', :super_operation => :fetch, hit: false } end read.name.must_equal('cache_read.active_support') read.payload.must_equal(read_payload) generate.name.must_equal('cache_generate.active_support') generate.payload.must_equal({ :key => 'radiohead' }) write.name.must_equal('cache_write.active_support') write.payload.must_equal({ :key => 'radiohead' }) end it "notifies on #read" do with_notifications do @store.read "metallica" end read = @events.first read.name.must_equal('cache_read.active_support') read.payload.must_equal({ :key => 'metallica', :hit => false }) end it "notifies on #write" do with_notifications do @store.write "depeche mode", "Enjoy The Silence" end write = @events.first write.name.must_equal('cache_write.active_support') write.payload.must_equal({ :key => 'depeche mode' }) end it "notifies on #delete" do with_notifications do @store.delete "the new cardigans" end delete = @events.first delete.name.must_equal('cache_delete.active_support') delete.payload.must_equal({ :key => 'the new cardigans' }) end it "notifies on #exist?" do with_notifications do @store.exist? "the smiths" end exist = @events.first exist.name.must_equal('cache_exist?.active_support') exist.payload.must_equal({ :key => 'the smiths' }) end it "notifies on #read_multi" do @store.write "depeche mode", "Enjoy The Silence" with_notifications do @store.read_multi "metallica", "depeche mode" end read = @events.first read.name.must_equal('cache_read_multi.active_support') read.payload.must_equal({ :key => ["metallica", "depeche mode"], :hits => ["depeche mode"] }) end it "notifies on #delete_matched" do with_notifications do @store.delete_matched "afterhours*" end delete_matched = @events.first delete_matched.name.must_equal('cache_delete_matched.active_support') delete_matched.payload.must_equal({ :key => %("afterhours*") }) end it "notifies on #increment" do with_notifications do @store.increment "pearl jam" end increment = @events.first increment.name.must_equal('cache_increment.active_support') increment.payload.must_equal({ :key => 'pearl jam', :amount => 1 }) end it "notifies on #decrement" do with_notifications do @store.decrement "placebo" end decrement = @events.first decrement.name.must_equal('cache_decrement.active_support') decrement.payload.must_equal({ :key => 'placebo', :amount => 1 }) end # it "notifies on cleanup" # TODO implement in ActiveSupport::Cache::RedisStore it "should notify on clear" do with_notifications do @store.clear end clear = @events.first clear.name.must_equal('cache_clear.active_support') clear.payload.must_equal({ :key => nil }) end end describe "raise_errors => true" do def setup @raise_error_store = ActiveSupport::Cache::RedisStore.new("redis://127.0.0.1:6380/1", :raise_errors => true) @raise_error_store.stubs(:with).raises(Redis::CannotConnectError) end it "raises on read when redis is unavailable" do assert_raises(Redis::CannotConnectError) do @raise_error_store.read("rabbit") end end it "raises on read_multi when redis is unavailable" do assert_raises(Redis::CannotConnectError) do @raise_error_store.read_multi("rabbit", "white-rabbit") end end it "raises on fetch_multi when redis is unavailable" do assert_raises(Redis::CannotConnectError) do @raise_error_store.fetch_multi("rabbit", "white-rabbit") do |key| key.upcase end end end it "raises on writes when redis is unavailable" do assert_raises(Redis::CannotConnectError) do @raise_error_store.write "rabbit", @white_rabbit, :expires_in => 1.second end end it "raises on delete when redis is unavailable" do assert_raises(Redis::CannotConnectError) do @raise_error_store.delete "rabbit" end end it "raises on delete_matched when redis is unavailable" do assert_raises(Redis::CannotConnectError) do @raise_error_store.delete_matched "rabb*" end end end describe "raise_errors => false" do def setup @raise_error_store = ActiveSupport::Cache::RedisStore.new("redis://127.0.0.1:6380/1") @raise_error_store.stubs(:with).raises(Redis::CannotConnectError) end it "returns nil from read when redis is unavailable" do @raise_error_store.read("rabbit").must_be_nil end it "returns empty hash from read_multi when redis is unavailable" do @raise_error_store.read_multi("rabbit", "white-rabbit").must_equal({}) end it "returns result hash from fetch_multi when redis is unavailable" do @raise_error_store.fetch_multi("rabbit", "white-rabbit") do |key| key.upcase end.must_equal({ "rabbit" => "RABBIT", "white-rabbit" => "WHITE-RABBIT", }) end it "returns false when redis is unavailable" do @raise_error_store.write("rabbit", @white_rabbit, :expires_in => 1.second).must_equal(false) end it "returns false when redis is unavailable" do @raise_error_store.delete("rabbit").must_equal(false) end it "raises on delete_matched when redis is unavailable" do @raise_error_store.delete_matched("rabb*").must_equal(false) end end private def instantiate_store(*addresses) ActiveSupport::Cache::RedisStore.new(*addresses).instance_variable_get(:@data) end def with_store_management yield @store yield @dstore yield @pool_store yield @external_pool_store end def with_notifications @events = [ ] ActiveSupport::Cache::RedisStore.instrument = true if instrument? ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args| @events << ActiveSupport::Notifications::Event.new(*args) end yield ensure ActiveSupport::Cache::RedisStore.instrument = false if instrument? end # ActiveSupport::Cache.instrument is always +true+ since Rails 4.2.0 def instrument? ActiveSupport::VERSION::MAJOR < 4 || ActiveSupport::VERSION::MAJOR == 4 && ActiveSupport::VERSION::MINOR < 2 end end redis-activesupport-5.0.4/.gitignore0000644000004100000410000000005713205552634017563 0ustar www-datawww-dataGemfile.lock *.gem tmp/ gemfiles/*.lock stdout redis-activesupport-5.0.4/README.md0000644000004100000410000000251213205552634017050 0ustar www-datawww-data# Redis stores for ActiveSupport __`redis-activesupport`__ provides a cache for __ActiveSupport__. See the main [redis-store readme](https://github.com/redis-store/redis-store) for general guidelines. ## Installation ```ruby # Gemfile gem 'redis-activesupport' ``` ## Usage If you are using redis-store with Rails, consider using the [redis-rails gem](https://github.com/redis-store/redis-rails) instead. For standalone usage: ```ruby ActiveSupport::Cache.lookup_store :redis_store # { ... optional configuration ... } ``` ## Running tests ```shell gem install bundler git clone git://github.com/redis-store/redis-activesupport.git cd redis-activesupport bundle install bundle exec rake ``` If you are on **Snow Leopard** you have to run `env ARCHFLAGS="-arch x86_64" bundle exec rake` ## Status [![Gem Version](https://badge.fury.io/rb/redis-activesupport.png)](http://badge.fury.io/rb/redis-activesupport) [![Build Status](https://secure.travis-ci.org/redis-store/redis-activesupport.png?branch=master)](http://travis-ci.org/redis-store/redis-activesupport?branch=master) [![Code Climate](https://codeclimate.com/github/redis-store/redis-activesupport.png)](https://codeclimate.com/github/redis-store/redis-activesupport) ## Copyright 2009 - 2013 Luca Guidi - [http://lucaguidi.com](http://lucaguidi.com), released under the MIT license