cache 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|