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 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
- ## Supported methods
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
- ## Rationale
74
+ ### Supported clients
29
75
 
30
- 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.
76
+ Supported memcached clients:
31
77
 
32
- * I'm tired of rescuing from Memcached::NotFound
33
- * I'm tired of forgetting whether it's :expires_in or :ttl
34
- * I don't know why we ever started using read/write instead of get/set.
35
- * I don't like how you have to manually handle after_fork for Redis, Memcached, etc.
36
- * I don't know why Memcached::Rails isn't implemented as an ActiveRecord::Cache::Store (Dalli did it just fine!)
37
- * Why are you asking me about :raw or whatever? Just marshal it
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
- Don't know why you would ever want to do this:
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
- new client
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, options_ignored_except_expires_in = {}, &blk)
106
- storage.fetch k, options_ignored_except_expires_in[:expires_in], &blk
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
- def write(k, v, options_ignored_except_expires_in = {}) #:nodoc:
128
- storage.set k, v, options_ignored_except_expires_in[:expires_in]
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
- def read_multi(*ks) #:nodoc:
136
- ks.map { |k| storage.get k }
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
@@ -1,3 +1,3 @@
1
1
  class Cache
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
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 test_read_multi
125
+ def test_get_multi
122
126
  @cache.set 'hello', 'world'
123
127
  @cache.set 'privyet', 'mir'
124
- assert_equal %w{world mir}, @cache.read_multi('hello', 'privyet')
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: 23
4
+ hash: 21
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 0
10
- version: 0.2.0
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-04 00:00:00 -06:00
18
+ date: 2011-03-07 00:00:00 -06:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency