redis-activesupport-5.3.0/0000755000004100000410000000000014172604600015563 5ustar www-datawww-dataredis-activesupport-5.3.0/.travis.yml0000644000004100000410000000217114172604600017675 0ustar www-datawww-datalanguage: ruby script: bundle exec rake rvm: - 2.0 - 2.1 - 2.3 - 2.4 - 2.5 - 2.6 - ruby-head - jruby-head services: - redis-server matrix: allow_failures: - rvm: jruby-head - rvm: ruby-head exclude: - rvm: 2.0 gemfile: gemfiles/activesupport_50.gemfile - rvm: 2.1 gemfile: gemfiles/activesupport_50.gemfile - rvm: 2.0 gemfile: gemfiles/activesupport_51.gemfile - rvm: 2.1 gemfile: gemfiles/activesupport_51.gemfile - rvm: 2.0 gemfile: gemfiles/activesupport_52.gemfile - rvm: 2.1 gemfile: gemfiles/activesupport_52.gemfile notifications: webhooks: https://www.travisbuddy.com/ on_success: never gemfile: - gemfiles/activesupport_3.gemfile - gemfiles/activesupport_4.gemfile - gemfiles/activesupport_50.gemfile - gemfiles/activesupport_51.gemfile - gemfiles/activesupport_52.gemfile deploy: provider: rubygems api_key: secure: VHWLUgCtqlKjeS5uGOxS4tnEUSPiapyvBvgSpr+FUeQnjAE9jgJvz+rAmiNy/pp8fAhjH5FdyIUXuh2rE2sWcBYrOa1rCvrc7eBHdnpZ4U7ULJwQKhC/4dOE33ClaZX2pex4pv12I2218ZH5TsqdmQ0Ci0ccfNZJv0vs+IFP+kQ= gem: redis-activesupport on: tags: true repo: redis-store/redis-activesupport redis-activesupport-5.3.0/test/0000755000004100000410000000000014172604600016542 5ustar www-datawww-dataredis-activesupport-5.3.0/test/active_support/0000755000004100000410000000000014172604600021611 5ustar www-datawww-dataredis-activesupport-5.3.0/test/active_support/cache/0000755000004100000410000000000014172604600022654 5ustar www-datawww-dataredis-activesupport-5.3.0/test/active_support/cache/redis_store_test.rb0000644000004100000410000006044614172604600026574 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 "uses redis client passed as an option" do redis = Redis.new(url: "redis://127.0.0.1:6380/1") store = ActiveSupport::Cache::RedisStore.new(client: redis) store.data.must_equal(redis) 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 nil" do underlying_store = instantiate_store nil 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 "increments a key with expiration time" do with_store_management do |store| store.increment "counter", 1, :expires_in => 1.second store.read("counter", :raw => true).to_i.must_equal(1) sleep 2 store.read("counter", :raw => true).must_be_nil end end it "decrements a key with expiration time" do with_store_management do |store| store.decrement "counter", 1, :expires_in => 1.second store.read("counter", :raw => true).to_i.must_equal(-1) sleep 2 store.read("counter", :raw => true).must_be_nil 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, version: Time.now.to_i } @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, version: Time.now.to_i } @store.fetch("rabbit", options) { @rabbit } sleep 1 th1 = Thread.new do first_rabbit = @store.fetch("rabbit", options) 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.3.0/test/test_helper.rb0000644000004100000410000000032614172604600021406 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.3.0/CODEOWNERS0000644000004100000410000000001114172604600017146 0ustar www-datawww-data* @tubbo redis-activesupport-5.3.0/README.md0000644000004100000410000000372214172604600017046 0ustar www-datawww-data# This gem is in LTS mode. Rails 5.2.0 [includes a Redis cache store out of the box](https://github.com/rails/rails/pull/31134), so you don't really need this anymore if you're generating a new Rails application. We are no longer accepting new features for this gem, only security updates will be considered for new pull requests. # Redis stores for ActiveSupport __`redis-activesupport`__ provides a cache for __ActiveSupport__. For guidelines on using our underlying cache store, see the main [redis-store readme](https://github.com/redis-store/redis-store). For information on how to use this library in a Rails app, see the [documentation for redis-rails](https://github.com/redis-store/redis-rails). If, for some reason, you're using `ActiveSupport::Cache` and not in a Rails app, read on to learn how to install/use this gem by itself! ## 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 redis-activesupport-5.3.0/gemfiles/0000755000004100000410000000000014172604600017356 5ustar www-datawww-dataredis-activesupport-5.3.0/gemfiles/activesupport_50.gemfile0000644000004100000410000000020714172604600024123 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "i18n" gem "activesupport", "~> 5.0.0" gemspec path: "../" redis-activesupport-5.3.0/gemfiles/activesupport_3.gemfile0000644000004100000410000000020314172604600024035 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "i18n" gem "activesupport", "~> 3" gemspec path: "../" redis-activesupport-5.3.0/gemfiles/activesupport_4.gemfile0000644000004100000410000000020314172604600024036 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "i18n" gem "activesupport", "~> 4" gemspec path: "../" redis-activesupport-5.3.0/gemfiles/activesupport_51.gemfile0000644000004100000410000000020714172604600024124 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "i18n" gem "activesupport", "~> 5.1.0" gemspec path: "../" redis-activesupport-5.3.0/gemfiles/activesupport_52.gemfile0000644000004100000410000000020714172604600024125 0ustar www-datawww-data# This file was generated by Appraisal source "https://rubygems.org" gem "i18n" gem "activesupport", "~> 5.2.0" gemspec path: "../" redis-activesupport-5.3.0/Appraisals0000644000004100000410000000052314172604600017605 0ustar www-datawww-dataappraise 'activesupport_3' do gem 'activesupport', '~> 3' end appraise 'activesupport_4' do gem 'activesupport', '~> 4' end appraise 'activesupport_50' do gem 'activesupport', '~> 5.0.0' end appraise 'activesupport_51' do gem 'activesupport', '~> 5.1.0' end appraise 'activesupport_52' do gem 'activesupport', '~> 5.2.0' end redis-activesupport-5.3.0/.gitignore0000644000004100000410000000007714172604600017557 0ustar www-datawww-dataGemfile.lock *.gem tmp/ gemfiles/*.lock stdout gemfiles/vendor redis-activesupport-5.3.0/Rakefile0000644000004100000410000000014714172604600017232 0ustar www-datawww-datarequire 'bundler/setup' require 'rake' require 'bundler/gem_tasks' require 'redis-store/testing/tasks' redis-activesupport-5.3.0/lib/0000755000004100000410000000000014172604600016331 5ustar www-datawww-dataredis-activesupport-5.3.0/lib/redis-activesupport.rb0000644000004100000410000000013214172604600022666 0ustar www-datawww-datarequire 'redis-store' require 'active_support' require 'active_support/cache/redis_store' redis-activesupport-5.3.0/lib/redis/0000755000004100000410000000000014172604600017437 5ustar www-datawww-dataredis-activesupport-5.3.0/lib/redis/active_support/0000755000004100000410000000000014172604600022506 5ustar www-datawww-dataredis-activesupport-5.3.0/lib/redis/active_support/version.rb0000644000004100000410000000010314172604600024512 0ustar www-datawww-dataclass Redis module ActiveSupport VERSION = '5.3.0' end end redis-activesupport-5.3.0/lib/active_support/0000755000004100000410000000000014172604600021400 5ustar www-datawww-dataredis-activesupport-5.3.0/lib/active_support/cache/0000755000004100000410000000000014172604600022443 5ustar www-datawww-dataredis-activesupport-5.3.0/lib/active_support/cache/redis_store.rb0000644000004100000410000002707714172604600025327 0ustar www-datawww-data# encoding: UTF-8 require 'redis-store' module ActiveSupport module Cache class RedisStore < Store ERRORS_TO_RESCUE = [ Errno::ECONNREFUSED, Errno::EHOSTUNREACH, # This is what rails' redis cache store rescues # https://github.com/rails/rails/blob/5-2-stable/activesupport/lib/active_support/cache/redis_cache_store.rb#L447 Redis::BaseConnectionError ].freeze DEFAULT_ERROR_HANDLER = -> (method: nil, returning: nil, exception: nil) do if logger logger.error { "RedisStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" } end end attr_reader :data # Instantiate the store. # # Example: # RedisStore.new # # => host: localhost, port: 6379, db: 0 # # RedisStore.new client: Redis.new(url: "redis://127.0.0.1:6380/1") # # => host: localhost, port: 6379, db: 0 # # RedisStore.new "redis://example.com" # # => host: example.com, port: 6379, db: 0 # # RedisStore.new "redis://example.com:23682" # # => host: example.com, port: 23682, db: 0 # # RedisStore.new "redis://example.com:23682/1" # # => host: example.com, port: 23682, db: 1 # # RedisStore.new "redis://example.com:23682/1/theplaylist" # # => host: example.com, port: 23682, db: 1, namespace: theplaylist # # RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0" # # => instantiate a cluster # # RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0", pool_size: 5, pool_timeout: 10 # # => use a ConnectionPool # # RedisStore.new "redis://localhost:6379/0", "redis://localhost:6380/0", # pool: ::ConnectionPool.new(size: 1, timeout: 1) { ::Redis::Store::Factory.create("localhost:6379/0") }) # # => supply an existing connection pool (e.g. for use with redis-sentinel or redis-failover) def initialize(*addresses) @options = addresses.extract_options! addresses = addresses.compact.map(&:dup) @data = if @options[:pool] raise "pool must be an instance of ConnectionPool" unless @options[:pool].is_a?(ConnectionPool) @pooled = true @options[:pool] elsif [:pool_size, :pool_timeout].any? { |key| @options.has_key?(key) } pool_options = {} pool_options[:size] = options[:pool_size] if options[:pool_size] pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] @pooled = true ::ConnectionPool.new(pool_options) { ::Redis::Store::Factory.create(*addresses, @options) } elsif @options[:client] @options[:client] else ::Redis::Store::Factory.create(*addresses, @options) end @error_handler = @options[:error_handler] || DEFAULT_ERROR_HANDLER super(@options) end def write(name, value, options = nil) options = merged_options(options.to_h.symbolize_keys) instrument(:write, name, options) do |_payload| if options[:expires_in].present? && options[:race_condition_ttl].present? && options[:raw].blank? options[:expires_in] = options[:expires_in].to_f + options[:race_condition_ttl].to_f end entry = options[:raw].present? ? value : Entry.new(value, **options) write_entry(normalize_key(name, options), entry, options) end end # 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 failsafe(:read_multi, returning: false) do matcher = key_matcher(matcher, options) begin with do |store| !(keys = store.keys(matcher)).empty? && store.del(*keys) end end end end end # 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| failsafe(:read_multi, returning: {}) do values = with { |c| c.mget(*args) } values.map! { |v| v.is_a?(ActiveSupport::Cache::Entry) ? v.value : v } Hash[names.zip(values)].reject{|k,v| v.nil?}.tap do |result| payload[:hits] = result.keys if payload end end end end def fetch_multi(*names) options = names.extract_options! return {} if names == [] results = read_multi(*names, options) need_writes = {} fetched = names.inject({}) do |memo, name| memo[name] = results.fetch(name) do value = yield name need_writes[name] = value value end memo end failsafe(:fetch_multi_write) do with do |c| c.multi do need_writes.each do |name, value| write(name, value, options) end end end end fetched end # Increment 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(name, amount = 1, options = {}) instrument :increment, name, amount: amount do failsafe :increment do options = merged_options(options) key = normalize_key(name, options) with do |c| c.incrby(key, amount).tap do write_key_expiry(c, key, options) end end end end end # 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(name, amount = 1, options = {}) instrument :decrement, name, amount: amount do failsafe :decrement do options = merged_options(options) key = normalize_key(name, options) with do |c| c.decrby(key, amount).tap do write_key_expiry(c, key, options) end end end end end 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 failsafe(:clear) do with(&:flushdb) end 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) failsafe(:write_entry, returning: false) do method = options && options[:unless_exist] ? :setnx : :set with { |client| client.send method, key, entry, options } end end def read_entry(key, options) failsafe(:read_entry) do entry = with { |c| c.get key, options } return unless entry entry.is_a?(Entry) ? entry : Entry.new(entry) end end def write_key_expiry(client, key, options) if options[:expires_in] && client.ttl(key) < 0 client.expire key, options[:expires_in].to_i end end ## # Implement the ActiveSupport::Cache#delete_entry # # It's really needed and use # def delete_entry(key, **options) failsafe(:delete_entry, returning: false) do with { |c| c.del key } end 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 def failsafe(method, returning: nil) yield rescue ::Redis::BaseConnectionError => e raise if raise_errors? handle_exception(exception: e, method: method, returning: returning) returning end def handle_exception(exception: nil, method: nil, returning: nil) if @error_handler @error_handler.(method: method, exception: exception, returning: returning) end rescue => failsafe warn("RedisStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}") end end end end redis-activesupport-5.3.0/Gemfile0000644000004100000410000000006214172604600017054 0ustar www-datawww-datasource 'https://rubygems.org' gemspec gem 'i18n' redis-activesupport-5.3.0/.github/0000755000004100000410000000000014172604600017123 5ustar www-datawww-dataredis-activesupport-5.3.0/.github/auto-assign-issues.yml0000644000004100000410000000002514172604600023406 0ustar www-datawww-dataassignees: - tubbo redis-activesupport-5.3.0/MIT-LICENSE0000644000004100000410000000204514172604600017220 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.3.0/redis-activesupport.gemspec0000644000004100000410000000243614172604600023151 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.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', '< 8' s.add_development_dependency 'rake', '>= 12.3.3' s.add_development_dependency 'bundler' s.add_development_dependency 'mocha', '~> 0.14.0' s.add_development_dependency 'minitest', '>= 4.2', '< 6' s.add_development_dependency 'connection_pool', '= 2.2.2' s.add_development_dependency 'redis-store-testing' s.add_development_dependency 'appraisal', '~> 2.0' s.add_development_dependency 'pry-byebug', '~> 3' end