cache 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +69 -52
- data/lib/cache.rb +38 -9
- data/lib/cache/storage.rb +16 -6
- data/lib/cache/version.rb +1 -1
- data/test/shared_tests.rb +6 -6
- metadata +4 -4
data/README.md
CHANGED
@@ -2,7 +2,52 @@
|
|
2
2
|
|
3
3
|
A unified cache handling interface, inspired by libraries like [ActiveSupport::Cache::Store](http://api.rubyonrails.org/classes/ActiveSupport/Cache/Store.html), Perl's [Cache::Cache](http://cpan.uwinnipeg.ca/module/Cache::Cache), and [CHI](http://cpan.uwinnipeg.ca/module/CHI).
|
4
4
|
|
5
|
-
##
|
5
|
+
## Quick example
|
6
|
+
|
7
|
+
require 'memcached' # a really fast memcached client gem
|
8
|
+
require 'cache' # this gem, which wraps the client to provide a standard interface
|
9
|
+
raw_client = Memcached.new('127.0.0.1:11211')
|
10
|
+
@cache = Cache.wrap(raw_client)
|
11
|
+
|
12
|
+
# don't worry, even though it's memcached gem, this won't raise Memcached::NotFound
|
13
|
+
@cache.get('hello')
|
14
|
+
|
15
|
+
# fetch is not provided by the memcached gem, the wrapper adds it
|
16
|
+
@cache.fetch('hello') { 'world' }
|
17
|
+
|
18
|
+
# don't worry, the wrapper will automatically clone the Memcached object after forking
|
19
|
+
Kernel.fork { @cache.get('hello') }
|
20
|
+
|
21
|
+
## Rationale
|
22
|
+
|
23
|
+
I wanted a common interface to a bunch of great Ruby cache clients so I can develop gems (lock_method, cache_method) that accept any of them.
|
24
|
+
|
25
|
+
* I'm tired of rescuing from Memcached::NotFound
|
26
|
+
* I'm tired of forgetting whether it's :expires_in or :ttl
|
27
|
+
* I don't know why we ever started using read/write instead of get/set.
|
28
|
+
* I don't like how you have to manually handle after_fork for Redis, Memcached, etc.
|
29
|
+
* I don't know why Memcached::Rails isn't implemented as an ActiveRecord::Cache::Store (Dalli did it just fine!)
|
30
|
+
* Why are you asking me about :raw or whatever? Just marshal it
|
31
|
+
|
32
|
+
## Features
|
33
|
+
|
34
|
+
### Forking/threading
|
35
|
+
|
36
|
+
When you use a Cache object to wrap Memcached or Redis, you don't have to worry about forking or threading.
|
37
|
+
|
38
|
+
For example, you don't have to set up unicorn or PhusionPassenger's <tt>after_fork</tt>.
|
39
|
+
|
40
|
+
### TTL
|
41
|
+
|
42
|
+
0 means don't expire.
|
43
|
+
|
44
|
+
The default ttl is 60 seconds.
|
45
|
+
|
46
|
+
### Marshalling
|
47
|
+
|
48
|
+
Everything gets marshalled. No option to turn it into "raw" mode. If you need that kind of control, please submit a patch or just use one of the other gems directly.
|
49
|
+
|
50
|
+
### Supported methods
|
6
51
|
|
7
52
|
It will translate these methods to whatever Redis, Memcached, etc. client you're using:
|
8
53
|
|
@@ -16,6 +61,7 @@ It will translate these methods to whatever Redis, Memcached, etc. client you're
|
|
16
61
|
@cache.cas 'hello' { |current| 'world' }
|
17
62
|
@cache.increment 'high-fives'
|
18
63
|
@cache.decrement 'high-fives'
|
64
|
+
@cache.get_multi 'hello', 'privyet', 'hallo'
|
19
65
|
|
20
66
|
Also provided for Rails compatibility:
|
21
67
|
|
@@ -25,16 +71,17 @@ Also provided for Rails compatibility:
|
|
25
71
|
@cache.compare_and_swap
|
26
72
|
@cache.read_multi 'hello', 'privyet', 'hallo'
|
27
73
|
|
28
|
-
|
74
|
+
### Supported clients
|
29
75
|
|
30
|
-
|
76
|
+
Supported memcached clients:
|
31
77
|
|
32
|
-
*
|
33
|
-
*
|
34
|
-
*
|
35
|
-
|
36
|
-
|
37
|
-
|
78
|
+
* [memcached](https://github.com/fauna/memcached) (native C extensions, super fast!)
|
79
|
+
* [dalli](https://github.com/mperham/dalli) (pure ruby, recommended if you're on heroku)
|
80
|
+
* [memcache-client](https://github.com/mperham/memcache-client) (not recommended. the one that comes with Rails.)
|
81
|
+
|
82
|
+
Supported Redis clients:
|
83
|
+
|
84
|
+
* [redis](https://github.com/ezmobius/redis-rb)
|
38
85
|
|
39
86
|
## How you might use it
|
40
87
|
|
@@ -51,18 +98,6 @@ I wanted a common interface to a bunch of great Ruby cache clients so I can deve
|
|
51
98
|
<td><pre>config.cache_store = Cache.wrap(Dalli::Client.new)</pre></td>
|
52
99
|
<td><pre>config.cache_store = Cache.wrap(Redis.new)</pre></td>
|
53
100
|
</tr>
|
54
|
-
<tr>
|
55
|
-
<td><a href="https://github.com/seamusabshere/cache_method">CacheMethod</a> (already uses Cache internally)</td>
|
56
|
-
<td><pre>CacheMethod.config.storage = Memcached.new</pre></td>
|
57
|
-
<td><pre>CacheMethod.config.storage = Dalli::Client.new</pre></td>
|
58
|
-
<td><pre>CacheMethod.config.storage = Redis.new</pre></td>
|
59
|
-
</tr>
|
60
|
-
<tr>
|
61
|
-
<td><a href="https://github.com/seamusabshere/lock_method">LockMethod</a> (already uses Cache internally)</td>
|
62
|
-
<td><pre>LockMethod.config.storage = Memcached.new</pre></td>
|
63
|
-
<td><pre>LockMethod.config.storage = Dalli::Client.new</pre></td>
|
64
|
-
<td><pre>LockMethod.config.storage = Redis.new</pre></td>
|
65
|
-
</tr>
|
66
101
|
<tr>
|
67
102
|
<td>Your own library</td>
|
68
103
|
<td>
|
@@ -90,18 +125,20 @@ end
|
|
90
125
|
</pre>
|
91
126
|
</td>
|
92
127
|
</tr>
|
128
|
+
<tr>
|
129
|
+
<td><a href="https://github.com/seamusabshere/cache_method">CacheMethod</a> (already uses Cache internally)</td>
|
130
|
+
<td><pre>CacheMethod.config.storage = Memcached.new</pre></td>
|
131
|
+
<td><pre>CacheMethod.config.storage = Dalli::Client.new</pre></td>
|
132
|
+
<td><pre>CacheMethod.config.storage = Redis.new</pre></td>
|
133
|
+
</tr>
|
134
|
+
<tr>
|
135
|
+
<td><a href="https://github.com/seamusabshere/lock_method">LockMethod</a> (already uses Cache internally)</td>
|
136
|
+
<td><pre>LockMethod.config.storage = Memcached.new</pre></td>
|
137
|
+
<td><pre>LockMethod.config.storage = Dalli::Client.new</pre></td>
|
138
|
+
<td><pre>LockMethod.config.storage = Redis.new</pre></td>
|
139
|
+
</tr>
|
93
140
|
</table>
|
94
141
|
|
95
|
-
## Forking/threading
|
96
|
-
|
97
|
-
When you use a Cache object to wrap Memcached or Redis, you don't have to worry about forking or threading.
|
98
|
-
|
99
|
-
For example, you don't have to set up unicorn or PhusionPassenger's <tt>after_fork</tt>.
|
100
|
-
|
101
|
-
## TTL
|
102
|
-
|
103
|
-
0 means don't expire.
|
104
|
-
|
105
142
|
## Other examples
|
106
143
|
|
107
144
|
It defaults to an in-process memory store:
|
@@ -112,13 +149,6 @@ It defaults to an in-process memory store:
|
|
112
149
|
|
113
150
|
You can specify a more useful cache client:
|
114
151
|
|
115
|
-
require 'memcached' # a really fast memcached client gem
|
116
|
-
require 'cache' # this gem, which provides a standard interface
|
117
|
-
raw_client = Memcached.new '127.0.0.1:11211'
|
118
|
-
@cache = Cache.wrap(raw_client)
|
119
|
-
|
120
|
-
or
|
121
|
-
|
122
152
|
require 'redis' # the redis key-value store
|
123
153
|
require 'cache' # this gem, which provides a standard interface
|
124
154
|
raw_client = Redis.new
|
@@ -131,23 +161,10 @@ or
|
|
131
161
|
raw_client = Dalli::Client.new
|
132
162
|
@cache = Cache.wrap(raw_client)
|
133
163
|
|
134
|
-
|
164
|
+
Or you could piggyback off the default rails cache:
|
135
165
|
|
136
|
-
# Piggyback off the default rails cache
|
137
166
|
@cache = Cache.wrap(Rails.cache)
|
138
167
|
|
139
|
-
## Supported clients
|
140
|
-
|
141
|
-
Supported memcached clients:
|
142
|
-
|
143
|
-
* [memcached](https://github.com/fauna/memcached) (super fast!)
|
144
|
-
* [dalli](https://github.com/mperham/dalli) (pure ruby, recommended if you're on heroku)
|
145
|
-
* [memcache-client](https://github.com/mperham/memcache-client) (not recommended. the one that comes with Rails.)
|
146
|
-
|
147
|
-
Supported Redis clients:
|
148
|
-
|
149
|
-
* [redis](https://github.com/ezmobius/redis-rb)
|
150
|
-
|
151
168
|
## Copyright
|
152
169
|
|
153
170
|
Copyright 2011 Seamus Abshere
|
data/lib/cache.rb
CHANGED
@@ -19,7 +19,11 @@ class Cache
|
|
19
19
|
# raw_client = Memcached.new('127.0.0.1:11211')
|
20
20
|
# cache = Cache.wrap raw_client
|
21
21
|
def self.wrap(client = nil)
|
22
|
-
|
22
|
+
if client.is_a?(::Cache)
|
23
|
+
client
|
24
|
+
else
|
25
|
+
new client
|
26
|
+
end
|
23
27
|
end
|
24
28
|
|
25
29
|
def initialize(client = nil) #:nodoc:
|
@@ -48,7 +52,7 @@ class Cache
|
|
48
52
|
# cache.set 'hello', 'world'
|
49
53
|
# cache.set 'hello', 'world', 80 # seconds til it expires
|
50
54
|
def set(k, v, ttl = nil, ignored_options = nil)
|
51
|
-
storage.set k, v, ttl
|
55
|
+
storage.set k, v, extract_ttl(ttl)
|
52
56
|
end
|
53
57
|
|
54
58
|
# Delete a value.
|
@@ -100,10 +104,12 @@ class Cache
|
|
100
104
|
|
101
105
|
# Try to get a value and if it doesn't exist, set it to the result of the block.
|
102
106
|
#
|
107
|
+
# Accepts :expires_in for compatibility with Rails.
|
108
|
+
#
|
103
109
|
# Example:
|
104
110
|
# cache.fetch 'hello' { 'world' }
|
105
|
-
def fetch(k,
|
106
|
-
storage.fetch k,
|
111
|
+
def fetch(k, ttl = nil, &blk)
|
112
|
+
storage.fetch k, extract_ttl(ttl), &blk
|
107
113
|
end
|
108
114
|
|
109
115
|
# Get the current value (if any), pass it into a block, and set the result.
|
@@ -111,7 +117,7 @@ class Cache
|
|
111
117
|
# Example:
|
112
118
|
# cache.cas 'hello' { |current| 'world' }
|
113
119
|
def cas(k, ttl = nil, &blk)
|
114
|
-
storage.cas k, ttl, &blk
|
120
|
+
storage.cas k, extract_ttl(ttl), &blk
|
115
121
|
end
|
116
122
|
|
117
123
|
alias :compare_and_swap :cas
|
@@ -123,16 +129,39 @@ class Cache
|
|
123
129
|
def stats
|
124
130
|
storage.stats
|
125
131
|
end
|
132
|
+
|
133
|
+
# Get multiple cache entries.
|
134
|
+
#
|
135
|
+
# Example:
|
136
|
+
# cache.get_multi 'hello', 'privyet'
|
137
|
+
def get_multi(*ks)
|
138
|
+
storage.get_multi ks
|
139
|
+
end
|
126
140
|
|
127
|
-
|
128
|
-
|
141
|
+
# Like get, but accepts :expires_in for compatibility with Rails.
|
142
|
+
#
|
143
|
+
# In general, you should use get instead.
|
144
|
+
#
|
145
|
+
# Example:
|
146
|
+
# cache.write 'hello', 'world', :expires_in => 5.minutes
|
147
|
+
def write(k, v, ttl = nil)
|
148
|
+
storage.set k, v, extract_ttl(ttl)
|
129
149
|
end
|
130
150
|
|
131
151
|
def read(k, ignored_options = nil) #:nodoc:
|
132
152
|
storage.get k
|
133
153
|
end
|
134
154
|
|
135
|
-
|
136
|
-
|
155
|
+
private
|
156
|
+
|
157
|
+
def extract_ttl(ttl)
|
158
|
+
case ttl
|
159
|
+
when ::Hash
|
160
|
+
ttl[:expires_in]
|
161
|
+
when ::NilClass
|
162
|
+
config.default_ttl
|
163
|
+
else
|
164
|
+
ttl.to_i
|
165
|
+
end
|
137
166
|
end
|
138
167
|
end
|
data/lib/cache/storage.rb
CHANGED
@@ -25,10 +25,24 @@ class Cache
|
|
25
25
|
raise "Don't know how to GET with #{bare.inspect}"
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
def get_multi(ks)
|
30
|
+
reset_if_forked_or_threaded
|
31
|
+
if memcached?
|
32
|
+
bare.get ks
|
33
|
+
elsif memcached_rails? or dalli? or mem_cache?
|
34
|
+
bare.get_multi ks
|
35
|
+
elsif active_support_store?
|
36
|
+
bare.read_multi *ks
|
37
|
+
else
|
38
|
+
ks.inject({}) do |memo, k|
|
39
|
+
memo[k] = get k if exist? k
|
40
|
+
memo
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
28
44
|
|
29
45
|
def set(k, v, ttl)
|
30
|
-
ttl ||= parent.config.default_ttl
|
31
|
-
ttl = ttl.to_i
|
32
46
|
reset_if_forked_or_threaded
|
33
47
|
if memcached? or dalli? or memcached_rails? or mem_cache?
|
34
48
|
bare.set k, v, ttl
|
@@ -103,8 +117,6 @@ class Cache
|
|
103
117
|
end
|
104
118
|
|
105
119
|
def fetch(k, ttl, &blk)
|
106
|
-
ttl ||= parent.config.default_ttl
|
107
|
-
ttl = ttl.to_i
|
108
120
|
reset_if_forked_or_threaded
|
109
121
|
if dalli? or mem_cache?
|
110
122
|
bare.fetch k, ttl, &blk
|
@@ -122,8 +134,6 @@ class Cache
|
|
122
134
|
end
|
123
135
|
|
124
136
|
def cas(k, ttl, &blk)
|
125
|
-
ttl ||= parent.config.default_ttl
|
126
|
-
ttl = ttl.to_i
|
127
137
|
reset_if_forked_or_threaded
|
128
138
|
if memcached?
|
129
139
|
begin; bare.cas(k, ttl, &blk); rescue ::Memcached::NotFound; nil; end
|
data/lib/cache/version.rb
CHANGED
data/test/shared_tests.rb
CHANGED
@@ -70,6 +70,10 @@ module SharedTests
|
|
70
70
|
assert_equal 'world', @cache.fetch('hello') { 'world' }
|
71
71
|
end
|
72
72
|
|
73
|
+
def test_fetch_with_expires_in
|
74
|
+
assert_equal 'world', @cache.fetch('hello', :expires_in => 5) { 'world' }
|
75
|
+
end
|
76
|
+
|
73
77
|
def test_cas
|
74
78
|
toggle = lambda do |current|
|
75
79
|
current == 'on' ? 'off' : 'on'
|
@@ -118,13 +122,9 @@ module SharedTests
|
|
118
122
|
assert_equal -2, @cache.get('high-fives')
|
119
123
|
end
|
120
124
|
|
121
|
-
def
|
125
|
+
def test_get_multi
|
122
126
|
@cache.set 'hello', 'world'
|
123
127
|
@cache.set 'privyet', 'mir'
|
124
|
-
assert_equal
|
128
|
+
assert_equal({ 'hello' => 'world', 'privyet' => 'mir'}, @cache.get_multi('hello', 'privyet', 'yoyoyo'))
|
125
129
|
end
|
126
|
-
|
127
|
-
alias :test_clear :test_flush
|
128
|
-
|
129
|
-
alias :test_compare_and_swap :test_cas
|
130
130
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 1
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Seamus Abshere
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-03-
|
18
|
+
date: 2011-03-07 00:00:00 -06:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|