redis-store-pika 1.9.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.codeclimate.yml +6 -0
- data/.github/auto-assign-issues.yml +2 -0
- data/.github/workflows/ci.yml +64 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +132 -0
- data/Appraisals +19 -0
- data/CHANGELOG.md +667 -0
- data/CODEOWNERS +1 -0
- data/Gemfile +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +68 -0
- data/Rakefile +15 -0
- data/gemfiles/redis_4_0_x.gemfile +7 -0
- data/gemfiles/redis_4_1_x.gemfile +7 -0
- data/gemfiles/redis_4_6_x.gemfile +7 -0
- data/gemfiles/redis_4_x.gemfile +7 -0
- data/gemfiles/redis_5_x.gemfile +7 -0
- data/lib/redis/distributed_store.rb +68 -0
- data/lib/redis/store/factory.rb +111 -0
- data/lib/redis/store/interface.rb +29 -0
- data/lib/redis/store/namespace.rb +211 -0
- data/lib/redis/store/redis_version.rb +13 -0
- data/lib/redis/store/serialization.rb +67 -0
- data/lib/redis/store/ttl.rb +47 -0
- data/lib/redis/store/version.rb +13 -0
- data/lib/redis/store.rb +85 -0
- data/lib/redis-store-pika.rb +1 -0
- data/lib/redis-store.rb +1 -0
- data/redis-store.gemspec +35 -0
- data/test/redis/distributed_store_test.rb +111 -0
- data/test/redis/store/factory_test.rb +273 -0
- data/test/redis/store/interface_test.rb +27 -0
- data/test/redis/store/namespace_test.rb +316 -0
- data/test/redis/store/redis_version_test.rb +28 -0
- data/test/redis/store/serialization_test.rb +173 -0
- data/test/redis/store/ttl_test.rb +142 -0
- data/test/redis/store/version_test.rb +7 -0
- data/test/redis/store_test.rb +68 -0
- data/test/test_helper.rb +20 -0
- metadata +246 -0
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
# This is a fork!
|
2
|
+
|
3
|
+
This is a fork of official redis-store gem published to let people use fixes unavailable on official one yet.
|
4
|
+
Be sure to check updates from official [`redis-store`](https://github.com/redis-store/redis-store) gem sometimes in case the fixes included in this fork are already merged there.
|
5
|
+
(You can subscribe specific issues/PRs/releases on GitHub)
|
6
|
+
|
7
|
+
To install this fork place `redis-store-pika` into Gemfile (no special `require` option needed, I think).
|
8
|
+
Be sure not to install/require `redis-store` at the same time (maybe comment that line out in `Gemfile`)
|
9
|
+
|
10
|
+
## Versioning
|
11
|
+
Since this is a fork (as a temporary measure) semantic versioning won't be used.
|
12
|
+
It would be like `(last offical version)` + `.x` (`x` = how many times it's released)
|
13
|
+
(e.g. Based on `1.9.2` + 1st release = `1.9.2.1`)
|
14
|
+
|
15
|
+
# Redis stores for Ruby frameworks
|
16
|
+
|
17
|
+
__Redis Store__ provides a full set of stores (*Cache*, *I18n*, *Session*, *HTTP Cache*) for modern Ruby frameworks like: __Ruby on Rails__, __Sinatra__, __Rack__, __Rack::Cache__ and __I18n__. It supports object marshalling, timeouts, single or multiple nodes, and namespaces.
|
18
|
+
|
19
|
+
Please check the *README* file of each gem for usage and installation guidelines.
|
20
|
+
|
21
|
+
## Redis Installation
|
22
|
+
|
23
|
+
### Option 1: Homebrew
|
24
|
+
|
25
|
+
MacOS X users should use [Homebrew](https://github.com/mxcl/homebrew) to install Redis:
|
26
|
+
|
27
|
+
```shell
|
28
|
+
brew install redis
|
29
|
+
```
|
30
|
+
|
31
|
+
### Option 2: From Source
|
32
|
+
|
33
|
+
Download and install Redis from [the download page](http://redis.io//download) and follow the instructions.
|
34
|
+
|
35
|
+
## Running tests
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
git clone git://github.com/redis-store/redis-store.git
|
39
|
+
cd redis-store
|
40
|
+
bundle install
|
41
|
+
bundle exec rake
|
42
|
+
```
|
43
|
+
|
44
|
+
If you are on **Snow Leopard** you have to run `env ARCHFLAGS="-arch x86_64" ruby ci/run.rb`
|
45
|
+
|
46
|
+
## Contributors
|
47
|
+
|
48
|
+
https://github.com/redis-store/redis-store/graphs/contributors
|
49
|
+
|
50
|
+
## Versioning
|
51
|
+
|
52
|
+
The **redis-store** family of gems uses [Semantic Versioning](http://semver.org), meaning gems depending on **redis-store**
|
53
|
+
can be reliably inclusive of any version between the current and the next major. We recommend the following dependency
|
54
|
+
in your library's gemspec:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
s.add_dependency 'redis-store', '>= 1.4', '< 2'
|
58
|
+
```
|
59
|
+
|
60
|
+
## Status
|
61
|
+
|
62
|
+
[![Gem Version](https://badge.fury.io/rb/redis-store.svg)](http://badge.fury.io/rb/redis-store)
|
63
|
+
[![Build Status](https://secure.travis-ci.org/redis-store/redis-store.svg?branch=master)](http://travis-ci.org/redis-store/redis-store?branch=master)
|
64
|
+
[![Code Climate](https://codeclimate.com/github/redis-store/redis-store.svg)](https://codeclimate.com/github/redis-store/redis-store)
|
65
|
+
|
66
|
+
## Copyright
|
67
|
+
|
68
|
+
2009 - 2013 Luca Guidi - [http://lucaguidi.com](http://lucaguidi.com), released under the MIT license.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'rake'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'appraisal'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
|
7
|
+
RuboCop::RakeTask.new :lint
|
8
|
+
|
9
|
+
if !ENV["APPRAISAL_INITIALIZED"] && !ENV["CI"]
|
10
|
+
task :default do
|
11
|
+
sh "appraisal install && rake appraisal default"
|
12
|
+
end
|
13
|
+
else
|
14
|
+
require 'redis-store/testing/tasks'
|
15
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'redis/distributed'
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class DistributedStore < Distributed
|
5
|
+
@@timeout = 5
|
6
|
+
attr_reader :ring
|
7
|
+
|
8
|
+
def initialize(addresses, options = {})
|
9
|
+
_extend_namespace options
|
10
|
+
# `@tag` introduced in `redis-rb` 5.0
|
11
|
+
@tag = options[:tag] || /^\{(.+?)\}/
|
12
|
+
@ring = options[:ring] || Redis::HashRing.new([], options[:replicas] || Redis::HashRing::POINTS_PER_SERVER)
|
13
|
+
|
14
|
+
addresses.each do |address|
|
15
|
+
@ring.add_node(::Redis::Store.new _merge_options(address, options))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def nodes
|
20
|
+
ring.nodes
|
21
|
+
end
|
22
|
+
|
23
|
+
def reconnect
|
24
|
+
nodes.each { |node| node.reconnect }
|
25
|
+
end
|
26
|
+
|
27
|
+
def set(key, value, options = nil)
|
28
|
+
node_for(key).set(key, value, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def get(key, options = nil)
|
32
|
+
node_for(key).get(key, options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def setnx(key, value, options = nil)
|
36
|
+
node_for(key).setnx(key, value, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def redis_version
|
40
|
+
nodes.first.redis_version unless nodes.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
def supports_redis_version?(version)
|
44
|
+
if nodes.empty?
|
45
|
+
false
|
46
|
+
else
|
47
|
+
nodes.first.supports_redis_version?(version)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def setex(key, expiry, value, options = nil)
|
52
|
+
node_for(key).setex(key, expiry, value, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
def _extend_namespace(options)
|
57
|
+
@namespace = options[:namespace]
|
58
|
+
extend ::Redis::Store::Namespace if @namespace
|
59
|
+
end
|
60
|
+
|
61
|
+
def _merge_options(address, options)
|
62
|
+
address.merge(
|
63
|
+
:timeout => options[:timeout] || @@timeout,
|
64
|
+
:namespace => options[:namespace]
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class Redis
|
5
|
+
class Store < self
|
6
|
+
class Factory
|
7
|
+
DEFAULT_PORT = 6379
|
8
|
+
|
9
|
+
def self.create(*options)
|
10
|
+
new(options).create
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(*options)
|
14
|
+
@addresses = []
|
15
|
+
@options = {}
|
16
|
+
extract_addresses_and_options(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create
|
20
|
+
if @addresses.empty?
|
21
|
+
@addresses << {}
|
22
|
+
end
|
23
|
+
|
24
|
+
if @addresses.size > 1
|
25
|
+
::Redis::DistributedStore.new @addresses, @options
|
26
|
+
else
|
27
|
+
::Redis::Store.new @addresses.first.merge(@options)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.resolve(uri) #:api: private
|
32
|
+
if uri.is_a?(Hash)
|
33
|
+
extract_host_options_from_hash(uri)
|
34
|
+
else
|
35
|
+
extract_host_options_from_uri(uri)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.extract_host_options_from_hash(options)
|
40
|
+
options = normalize_key_names(options)
|
41
|
+
if host_options?(options)
|
42
|
+
options
|
43
|
+
else
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.normalize_key_names(options)
|
49
|
+
options = options.dup
|
50
|
+
if options.key?(:key_prefix) && !options.key?(:namespace)
|
51
|
+
options[:namespace] = options.delete(:key_prefix) # RailsSessionStore
|
52
|
+
end
|
53
|
+
options[:raw] = case
|
54
|
+
when options.key?(:serializer)
|
55
|
+
options[:serializer].nil?
|
56
|
+
when options.key?(:marshalling)
|
57
|
+
!options[:marshalling]
|
58
|
+
else
|
59
|
+
false
|
60
|
+
end
|
61
|
+
options
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.host_options?(options)
|
65
|
+
options.keys.any? { |n| [:host, :db, :port, :path].include?(n) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.extract_host_options_from_uri(uri)
|
69
|
+
uri = URI.parse(uri)
|
70
|
+
if uri.scheme == "unix"
|
71
|
+
options = { :path => uri.path }
|
72
|
+
else
|
73
|
+
_, db, namespace = if uri.path
|
74
|
+
uri.path.split(/\//)
|
75
|
+
end
|
76
|
+
|
77
|
+
options = {
|
78
|
+
:scheme => uri.scheme,
|
79
|
+
:host => uri.hostname,
|
80
|
+
:port => uri.port || DEFAULT_PORT,
|
81
|
+
:password => uri.password.nil? ? nil : CGI.unescape(uri.password.to_s)
|
82
|
+
}
|
83
|
+
|
84
|
+
options[:db] = db.to_i if db
|
85
|
+
options[:namespace] = namespace if namespace
|
86
|
+
end
|
87
|
+
if uri.query
|
88
|
+
query = Hash[URI.decode_www_form(uri.query)]
|
89
|
+
query.each do |(key, value)|
|
90
|
+
options[key.to_sym] = value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
options
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def extract_addresses_and_options(*options)
|
100
|
+
options.flatten.compact.each do |token|
|
101
|
+
resolved = self.class.resolve(token)
|
102
|
+
if resolved
|
103
|
+
@addresses << resolved
|
104
|
+
else
|
105
|
+
@options.merge!(self.class.normalize_key_names(token))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Interface
|
4
|
+
def get(key, options = nil)
|
5
|
+
super(key)
|
6
|
+
end
|
7
|
+
|
8
|
+
REDIS_SET_OPTIONS = %i(ex px nx xx keepttl).freeze
|
9
|
+
private_constant :REDIS_SET_OPTIONS
|
10
|
+
|
11
|
+
def set(key, value, options = nil)
|
12
|
+
if options && REDIS_SET_OPTIONS.any? { |k| options.key?(k) }
|
13
|
+
kwargs = REDIS_SET_OPTIONS.each_with_object({}) { |key, h| h[key] = options[key] if options.key?(key) }
|
14
|
+
super(key, value, **kwargs)
|
15
|
+
else
|
16
|
+
super(key, value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def setnx(key, value, options = nil)
|
21
|
+
super(key, value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def setex(key, expiry, value, options = nil)
|
25
|
+
super(key, expiry, value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Namespace
|
4
|
+
FLUSHDB_BATCH_SIZE = 1000
|
5
|
+
|
6
|
+
def set(key, *args)
|
7
|
+
namespace(key) { |k| super(k, *args) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def setex(key, *args)
|
11
|
+
namespace(key) { |k| super(k, *args) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def setnx(key, *args)
|
15
|
+
namespace(key) { |k| super(k, *args) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def ttl(key, options = nil)
|
19
|
+
namespace(key) { |k| super(k) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def get(key, *args)
|
23
|
+
namespace(key) { |k| super(k, *args) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def exists(*keys)
|
27
|
+
super(*keys.map { |key| interpolate(key) })
|
28
|
+
end
|
29
|
+
|
30
|
+
def exists?(*keys)
|
31
|
+
super(*keys.map { |key| interpolate(key) })
|
32
|
+
end
|
33
|
+
|
34
|
+
def incrby(key, increment)
|
35
|
+
namespace(key) { |k| super(k, increment) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def decrby(key, increment)
|
39
|
+
namespace(key) { |k| super(k, increment) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def keys(pattern = "*")
|
43
|
+
namespace(pattern) { |p| super(p).map { |key| strip_namespace(key) } }
|
44
|
+
end
|
45
|
+
|
46
|
+
def scan(cursor, match: nil, **kwargs)
|
47
|
+
if match
|
48
|
+
namespace(match) do |pattern|
|
49
|
+
cursor, keys = super(cursor, match: pattern, **kwargs)
|
50
|
+
[ cursor, keys.map { |key| strip_namespace(key) } ]
|
51
|
+
end
|
52
|
+
else
|
53
|
+
super(cursor, **kwargs)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def del(*keys)
|
58
|
+
super(*keys.map { |key| interpolate(key) }) if keys.any?
|
59
|
+
end
|
60
|
+
|
61
|
+
def unlink(*keys)
|
62
|
+
super(*keys.map { |key| interpolate(key) }) if keys.any?
|
63
|
+
end
|
64
|
+
|
65
|
+
def watch(*keys)
|
66
|
+
super(*keys.map { |key| interpolate(key) }) if keys.any?
|
67
|
+
end
|
68
|
+
|
69
|
+
def mget(*keys, &blk)
|
70
|
+
options = (keys.pop if keys.last.is_a? Hash) || {}
|
71
|
+
if keys.any?
|
72
|
+
# Serialization gets extended before Namespace does, so we need to pass options further
|
73
|
+
if singleton_class.ancestors.include? Serialization
|
74
|
+
super(*keys.map { |key| interpolate(key) }, options, &blk)
|
75
|
+
else
|
76
|
+
super(*keys.map { |key| interpolate(key) }, &blk)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def expire(key, ttl)
|
82
|
+
namespace(key) { |k| super(k, ttl) }
|
83
|
+
end
|
84
|
+
|
85
|
+
def hdel(key, *fields)
|
86
|
+
namespace(key) { |k| super(k, *fields) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def hget(key, field)
|
90
|
+
namespace(key) { |k| super(k, field) }
|
91
|
+
end
|
92
|
+
|
93
|
+
def hgetall(key)
|
94
|
+
namespace(key) { |k| super(k) }
|
95
|
+
end
|
96
|
+
|
97
|
+
def hexists(key, field)
|
98
|
+
namespace(key) { |k| super(k, field) }
|
99
|
+
end
|
100
|
+
|
101
|
+
def hincrby(key, field, increment)
|
102
|
+
namespace(key) { |k| super(k, field, increment) }
|
103
|
+
end
|
104
|
+
|
105
|
+
def hincrbyfloat(key, field, increment)
|
106
|
+
namespace(key) { |k| super(k, field, increment) }
|
107
|
+
end
|
108
|
+
|
109
|
+
def hkeys(key)
|
110
|
+
namespace(key) { |k| super(k) }
|
111
|
+
end
|
112
|
+
|
113
|
+
def hlen(key)
|
114
|
+
namespace(key) { |k| super(k) }
|
115
|
+
end
|
116
|
+
|
117
|
+
def hmget(key, *fields, &blk)
|
118
|
+
namespace(key) { |k| super(k, *fields, &blk) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def hmset(key, *attrs)
|
122
|
+
namespace(key) { |k| super(k, *attrs) }
|
123
|
+
end
|
124
|
+
|
125
|
+
def hset(key, *args)
|
126
|
+
namespace(key) { |k| super(k, *args) }
|
127
|
+
end
|
128
|
+
|
129
|
+
def hsetnx(key, field, val)
|
130
|
+
namespace(key) { |k| super(k, field, val) }
|
131
|
+
end
|
132
|
+
|
133
|
+
def hvals(key)
|
134
|
+
namespace(key) { |k| super(k) }
|
135
|
+
end
|
136
|
+
|
137
|
+
def hscan(key, *args)
|
138
|
+
namespace(key) { |k| super(k, *args) }
|
139
|
+
end
|
140
|
+
|
141
|
+
def hscan_each(key, *args)
|
142
|
+
namespace(key) { |k| super(k, *args) }
|
143
|
+
end
|
144
|
+
|
145
|
+
def zincrby(key, increment, member)
|
146
|
+
namespace(key) { |k| super(k, increment, member) }
|
147
|
+
end
|
148
|
+
|
149
|
+
def zscore(key, member)
|
150
|
+
namespace(key) { |k| super(k, member) }
|
151
|
+
end
|
152
|
+
|
153
|
+
def zadd(key, *args)
|
154
|
+
namespace(key) { |k| super(k, *args) }
|
155
|
+
end
|
156
|
+
|
157
|
+
def zrem(key, member)
|
158
|
+
namespace(key) { |k| super(k, member) }
|
159
|
+
end
|
160
|
+
|
161
|
+
if respond_to?(:ruby2_keywords, true)
|
162
|
+
ruby2_keywords :set, :setex, :setnx, :hscan, :hscan_each
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_s
|
166
|
+
if namespace_str
|
167
|
+
"#{super} with namespace #{namespace_str}"
|
168
|
+
else
|
169
|
+
super
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def flushdb
|
174
|
+
return super unless namespace_str
|
175
|
+
keys.each_slice(FLUSHDB_BATCH_SIZE) { |key_slice| del(*key_slice) }
|
176
|
+
end
|
177
|
+
|
178
|
+
def with_namespace(ns)
|
179
|
+
old_ns = @namespace
|
180
|
+
@namespace = ns
|
181
|
+
yield self
|
182
|
+
ensure
|
183
|
+
@namespace = old_ns
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
def namespace(key)
|
188
|
+
yield interpolate(key)
|
189
|
+
end
|
190
|
+
|
191
|
+
def namespace_str
|
192
|
+
@namespace.is_a?(Proc) ? @namespace.call : @namespace
|
193
|
+
end
|
194
|
+
|
195
|
+
def interpolate(key)
|
196
|
+
return key unless namespace_str
|
197
|
+
key.match(namespace_regexp) ? key : "#{namespace_str}:#{key}"
|
198
|
+
end
|
199
|
+
|
200
|
+
def strip_namespace(key)
|
201
|
+
return key unless namespace_str
|
202
|
+
key.gsub namespace_regexp, ""
|
203
|
+
end
|
204
|
+
|
205
|
+
def namespace_regexp
|
206
|
+
@namespace_regexps ||= {}
|
207
|
+
@namespace_regexps[namespace_str] ||= %r{^#{namespace_str}\:}
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module RedisVersion
|
4
|
+
def redis_version
|
5
|
+
info('server')['redis_version']
|
6
|
+
end
|
7
|
+
|
8
|
+
def supports_redis_version?(version)
|
9
|
+
(redis_version.split(".").map(&:to_i) <=> version.split(".").map(&:to_i)) >= 0
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Serialization
|
4
|
+
def set(key, value, options = nil)
|
5
|
+
_marshal(value, options) { |v| super encode(key), encode(v), options }
|
6
|
+
end
|
7
|
+
|
8
|
+
def setnx(key, value, options = nil)
|
9
|
+
_marshal(value, options) { |v| super encode(key), encode(v), options }
|
10
|
+
end
|
11
|
+
|
12
|
+
def setex(key, expiry, value, options = nil)
|
13
|
+
_marshal(value, options) { |v| super encode(key), expiry, encode(v), options }
|
14
|
+
end
|
15
|
+
|
16
|
+
def get(key, options = nil)
|
17
|
+
_unmarshal super(key), options
|
18
|
+
end
|
19
|
+
|
20
|
+
def mget(*keys, &blk)
|
21
|
+
options = keys.pop if keys.last.is_a?(Hash)
|
22
|
+
super(*keys) do |reply|
|
23
|
+
reply.map! { |value| _unmarshal value, options }
|
24
|
+
blk ? blk.call(reply) : reply
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def mset(*args)
|
29
|
+
options = args.pop if args.length.odd?
|
30
|
+
updates = []
|
31
|
+
args.each_slice(2) do |(key, value)|
|
32
|
+
updates << encode(key)
|
33
|
+
_marshal(value, options) { |v| updates << encode(v) }
|
34
|
+
end
|
35
|
+
super(*updates)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def _marshal(val, options)
|
40
|
+
yield marshal?(options) ? @serializer.dump(val) : val
|
41
|
+
end
|
42
|
+
|
43
|
+
def _unmarshal(val, options)
|
44
|
+
unmarshal?(val, options) ? @serializer.load(val) : val
|
45
|
+
end
|
46
|
+
|
47
|
+
def marshal?(options)
|
48
|
+
!(options && options[:raw])
|
49
|
+
end
|
50
|
+
|
51
|
+
def unmarshal?(result, options)
|
52
|
+
result && result.size > 0 && marshal?(options)
|
53
|
+
end
|
54
|
+
|
55
|
+
if defined?(Encoding)
|
56
|
+
def encode(string)
|
57
|
+
key = string.to_s.dup
|
58
|
+
key.force_encoding(Encoding::BINARY)
|
59
|
+
end
|
60
|
+
else
|
61
|
+
def encode(string)
|
62
|
+
string
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
module Ttl
|
4
|
+
def set(key, value, options = nil)
|
5
|
+
if ttl = expires_in(options)
|
6
|
+
setex(key, ttl.to_i, value, :raw => true)
|
7
|
+
else
|
8
|
+
super(key, value, options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def setnx(key, value, options = nil)
|
13
|
+
if ttl = expires_in(options)
|
14
|
+
setnx_with_expire(key, value, ttl.to_i, options)
|
15
|
+
else
|
16
|
+
super(key, value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def setnx_with_expire(key, value, ttl, options = {})
|
22
|
+
with_multi_or_pipelined(options) do |transaction|
|
23
|
+
if transaction.is_a?(Redis::Store) # for redis < 4.6
|
24
|
+
setnx(key, value, :raw => true)
|
25
|
+
expire(key, ttl)
|
26
|
+
else
|
27
|
+
transaction.setnx(key, value)
|
28
|
+
transaction.expire(key, ttl)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def expires_in(options)
|
35
|
+
if options
|
36
|
+
# Rack::Session Merb Rails/Sinatra
|
37
|
+
options[:expire_after] || options[:expires_in] || options[:expire_in]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def with_multi_or_pipelined(options, &block)
|
42
|
+
return pipelined(&block) if options.key?(:cluster) || options[:avoid_multi_commands]
|
43
|
+
multi(&block)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Redis
|
2
|
+
class Store < self
|
3
|
+
# Official version + `.x`
|
4
|
+
VERSION = '1.9.2.1'
|
5
|
+
|
6
|
+
def self.redis_client_defined?
|
7
|
+
# Doesn't work if declared as constant
|
8
|
+
# due to unpredictable gem loading order
|
9
|
+
# https://github.com/redis-rb/redis-client
|
10
|
+
defined?(::RedisClient::VERSION)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|