spymemcached_store 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 594001153c291de7b7060e24e9bb9ee0a84f00a7
4
+ data.tar.gz: fe9f4e4d15186eaa6d25aef478330d81ccc62542
5
+ SHA512:
6
+ metadata.gz: 6288b45b59a236d638c1b3102caf9ebfbfa6b230eadc61a8ab958b0715a1722bfff6252081bb19002d247eb3d9a0dd2f087a8d2fd0f0ea52ce85a40d6e626029
7
+ data.tar.gz: cf5fc673f81471b351d697fd286349c1a4c3cbaf605cf63296f4eb65e76b22fe2f7593b4c9a267dd68b7c7b403192f3270f48cd75ceeb9d7c0519f3271fac564
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ jruby-1.7.16
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in spymemcached_store.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Xiao Li swing1979@gmail.com
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # SpymemcachedStore
2
+
3
+ This is Rails 4 compatible cache store for [spymemcached.jruby](https://github.com/ThoughtWorksStudios/spymemcached.jruby)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'spymemcached_store'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install spymemcached_store
20
+
21
+ ## Usage
22
+
23
+ ActiveSupport::Cache.lookup_store(:spymemcached_store, :expires_in => 60, :namespace => 'app-namespace')
24
+
25
+ Supports all Rails cache store options, see [spymemcached.jruby](https://github.com/ThoughtWorksStudios/spymemcached.jruby) for additional options.
26
+
27
+ It is not recommended to use :compress and :compress_threshold options, as spymemcached.jruby does it by default.
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it ( https://github.com/ThoughtWorksStudios/spymemcached_store/fork )
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/*_test.rb']
7
+ t.warning = true
8
+ t.verbose = false
9
+ end
10
+
11
+ task :default => [:test]
@@ -0,0 +1,166 @@
1
+ require 'active_support/cache'
2
+ require 'spymemcached'
3
+
4
+ module ActiveSupport
5
+ module Cache
6
+ class SpymemcachedStore < ActiveSupport::Cache::Store
7
+
8
+ # namespace
9
+ def initialize(*addresses)
10
+ addresses = addresses.flatten
11
+ options = addresses.extract_options!
12
+ super(options)
13
+
14
+ unless [String, Spymemcached, NilClass].include?(addresses.first.class)
15
+ raise ArgumentError, "First argument must be an empty array, an array of hosts or a Spymemcached instance."
16
+ end
17
+ @client = if addresses.first.is_a?(Spymemcached)
18
+ addresses.first
19
+ else
20
+ mem_cache_options = options.dup
21
+ UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
22
+ Spymemcached.new(addresses, mem_cache_options)
23
+ end
24
+
25
+ extend Strategy::LocalCache
26
+ extend LocalCacheWithRaw
27
+ end
28
+
29
+ # Read multiple values at once from the cache. Options can be passed
30
+ # in the last argument.
31
+ #
32
+ # Some cache implementation may optimize this method.
33
+ #
34
+ # Returns a hash mapping the names provided to the values found.
35
+ def read_multi(*names)
36
+ options = names.extract_options!
37
+ options = merged_options(options)
38
+ keys_to_names = Hash[names.map{|name| [namespaced_key(name, options), name]}]
39
+ raw_values = @client.get_multi(keys_to_names.keys)
40
+ values = {}
41
+ raw_values.each do |key, value|
42
+ entry = deserialize_entry(value)
43
+ values[keys_to_names[key]] = entry.value unless entry.expired?
44
+ end
45
+ values
46
+ end
47
+
48
+ # Increment an integer value in the cache.
49
+ #
50
+ # Options are passed to the underlying cache implementation.
51
+ #
52
+ # All implementations may not support this method.
53
+ def increment(name, amount = 1, options = nil)
54
+ options = merged_options(options)
55
+
56
+ instrument(:increment, name, :amount => amount) do
57
+ @client.incr(name, amount)
58
+ end
59
+ rescue Spymemcached::Error => e
60
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
61
+ nil
62
+ end
63
+
64
+ # Decrement an integer value in the cache.
65
+ #
66
+ # Options are passed to the underlying cache implementation.
67
+ #
68
+ # All implementations may not support this method.
69
+ def decrement(name, amount = 1, options = nil)
70
+ options = merged_options(options)
71
+
72
+ instrument(:decrement, name, :amount => amount) do
73
+ @client.decr(name, amount)
74
+ end
75
+ rescue Spymemcached::Error => e
76
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
77
+ nil
78
+ end
79
+
80
+ # Clear the entire cache. Be careful with this method since it could
81
+ # affect other processes if shared cache is being used.
82
+ #
83
+ # The options hash is passed to the underlying cache implementation.
84
+ #
85
+ # All implementations may not support this method.
86
+ def clear(options = nil)
87
+ @client.flush_all
88
+ rescue Spymemcached::Error => e
89
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
90
+ nil
91
+ end
92
+
93
+ # Get the statistics from the memcached servers.
94
+ def stats
95
+ @client.stats
96
+ end
97
+
98
+ protected
99
+ # Read an entry from the cache implementation. Subclasses must implement
100
+ # this method.
101
+ def read_entry(key, options) # :nodoc:
102
+ deserialize_entry(@client.get(key))
103
+ rescue Spymemcached::Error => e
104
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
105
+ nil
106
+ end
107
+
108
+ # Write an entry to the cache implementation. Subclasses must implement
109
+ # this method.
110
+ def write_entry(key, entry, options) # :nodoc:
111
+ method = options && options[:unless_exist] ? :add : :set
112
+ value = options[:raw] ? entry.value.to_s : entry
113
+ expires_in = options[:expires_in].to_i
114
+ if expires_in > 0 && !options[:raw]
115
+ # Set the memcache expire a few minutes in the future to support race condition ttls on read
116
+ expires_in += 5.minutes
117
+ end
118
+ @client.send(method, key, value, expires_in)
119
+ rescue Spymemcached::Error => e
120
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
121
+ false
122
+ end
123
+
124
+ # Delete an entry from the cache implementation. Subclasses must
125
+ # implement this method.
126
+ def delete_entry(key, options) # :nodoc:
127
+ @client.delete(key)
128
+ rescue Spymemcached::Error => e
129
+ logger.error("Spymemcached::Error (#{e}): #{e.message}") if logger
130
+ false
131
+ end
132
+
133
+ protected
134
+ def deserialize_entry(raw_value)
135
+ if raw_value
136
+ entry = Marshal.load(raw_value) rescue raw_value
137
+ entry.is_a?(Entry) ? entry : Entry.new(entry)
138
+ else
139
+ nil
140
+ end
141
+ end
142
+
143
+ # Provide support for raw values in the local cache strategy.
144
+ module LocalCacheWithRaw # :nodoc:
145
+ protected
146
+ def read_entry(key, options)
147
+ entry = super
148
+ if options[:raw] && local_cache && entry
149
+ entry = deserialize_entry(entry.value)
150
+ end
151
+ entry
152
+ end
153
+
154
+ def write_entry(key, entry, options) # :nodoc:
155
+ retval = super
156
+ if options[:raw] && local_cache && retval
157
+ raw_entry = Entry.new(entry.value.to_s)
158
+ raw_entry.expires_at = entry.expires_at
159
+ local_cache.write_entry(key, raw_entry, options)
160
+ end
161
+ retval
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,5 @@
1
+ require "spymemcached_store/version"
2
+
3
+ module SpymemcachedStore
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,3 @@
1
+ module SpymemcachedStore
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'spymemcached_store/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "spymemcached_store"
8
+ spec.version = SpymemcachedStore::VERSION
9
+ spec.authors = ["Xiao Li"]
10
+ spec.email = ["swing1979@gmail.com"]
11
+ spec.summary = %q{Rails 3 & 4 cache store for spymemcached.jruby.}
12
+ spec.description = %q{Rails 3 & 4 cache store for spymemcached.jruby.}
13
+ spec.homepage = "https://github.com/ThoughtWorksStudios/spymemcached_store"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "spymemcached.jruby"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.7"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rack"
26
+ spec.add_development_dependency "mocha"
27
+ spec.add_development_dependency "activesupport"
28
+ spec.add_development_dependency 'minitest', '~> 5.1'
29
+
30
+ end
@@ -0,0 +1,50 @@
1
+ require 'dependencies_test_helpers'
2
+
3
+ module AutoloadingCacheBehavior
4
+ class E
5
+ end
6
+ class ClassFolder
7
+ class NestedClass
8
+ end
9
+
10
+ class SiblingClass
11
+ end
12
+ end
13
+
14
+ include DependenciesTestHelpers
15
+ def test_simple_autoloading
16
+ with_autoloading_fixtures do
17
+ @cache.write('foo', E.new)
18
+ end
19
+
20
+ remove_constants(:E)
21
+ ActiveSupport::Dependencies.clear
22
+
23
+ with_autoloading_fixtures do
24
+ assert_kind_of E, @cache.read('foo')
25
+ end
26
+
27
+ remove_constants(:E)
28
+ ActiveSupport::Dependencies.clear
29
+ end
30
+
31
+ def test_two_classes_autoloading
32
+ with_autoloading_fixtures do
33
+ @cache.write('foo', [E.new, ClassFolder.new])
34
+ end
35
+
36
+ remove_constants(:E, :ClassFolder)
37
+ ActiveSupport::Dependencies.clear
38
+
39
+ with_autoloading_fixtures do
40
+ loaded = @cache.read('foo')
41
+ assert_kind_of Array, loaded
42
+ assert_equal 2, loaded.size
43
+ assert_kind_of E, loaded[0]
44
+ assert_kind_of ClassFolder, loaded[1]
45
+ end
46
+
47
+ remove_constants(:E, :ClassFolder)
48
+ ActiveSupport::Dependencies.clear
49
+ end
50
+ end
@@ -0,0 +1,24 @@
1
+ module CacheIncrementDecrementBehavior
2
+ def test_increment
3
+ @cache.write('foo', 1, :raw => true)
4
+ assert_equal 1, @cache.read('foo').to_i
5
+ assert_equal 2, @cache.increment('foo')
6
+ assert_equal 2, @cache.read('foo').to_i
7
+ assert_equal 3, @cache.increment('foo')
8
+ assert_equal 3, @cache.read('foo').to_i
9
+ # spymemcached will set zero as default value
10
+ assert_equal 0, @cache.increment('bar')
11
+ end
12
+
13
+ def test_decrement
14
+ @cache.write('foo', 3, :raw => true)
15
+ assert_equal 3, @cache.read('foo').to_i
16
+ assert_equal 2, @cache.decrement('foo')
17
+ assert_equal 2, @cache.read('foo').to_i
18
+ assert_equal 1, @cache.decrement('foo')
19
+ assert_equal 1, @cache.read('foo').to_i
20
+
21
+ # spymemcached will set zero as default value
22
+ assert_equal 0, @cache.decrement('bar')
23
+ end
24
+ end
@@ -0,0 +1,248 @@
1
+ module CacheStoreBehavior
2
+ def test_should_read_and_write_strings
3
+ assert @cache.write('foo', 'bar')
4
+ assert_equal 'bar', @cache.read('foo')
5
+ end
6
+
7
+ def test_should_overwrite
8
+ @cache.write('foo', 'bar')
9
+ @cache.write('foo', 'baz')
10
+ assert_equal 'baz', @cache.read('foo')
11
+ end
12
+
13
+ def test_fetch_without_cache_miss
14
+ @cache.write('foo', 'bar')
15
+ @cache.expects(:write).never
16
+ assert_equal 'bar', @cache.fetch('foo') { 'baz' }
17
+ end
18
+
19
+ def test_fetch_with_cache_miss
20
+ @cache.expects(:write).with('foo', 'baz', @cache.options)
21
+ assert_equal 'baz', @cache.fetch('foo') { 'baz' }
22
+ end
23
+
24
+ def test_fetch_with_cache_miss_passes_key_to_block
25
+ cache_miss = false
26
+ assert_equal 3, @cache.fetch('foo') { |key| cache_miss = true; key.length }
27
+ assert cache_miss
28
+
29
+ cache_miss = false
30
+ assert_equal 3, @cache.fetch('foo') { |key| cache_miss = true; key.length }
31
+ assert !cache_miss
32
+ end
33
+
34
+ def test_fetch_with_forced_cache_miss
35
+ @cache.write('foo', 'bar')
36
+ @cache.expects(:read).never
37
+ @cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true))
38
+ @cache.fetch('foo', :force => true) { 'bar' }
39
+ end
40
+
41
+ def test_fetch_with_cached_nil
42
+ @cache.write('foo', nil)
43
+ @cache.expects(:write).never
44
+ assert_nil @cache.fetch('foo') { 'baz' }
45
+ end
46
+
47
+ def test_should_read_and_write_hash
48
+ assert @cache.write('foo', {:a => "b"})
49
+ assert_equal({:a => "b"}, @cache.read('foo'))
50
+ end
51
+
52
+ def test_should_read_and_write_integer
53
+ assert @cache.write('foo', 1)
54
+ assert_equal 1, @cache.read('foo')
55
+ end
56
+
57
+ def test_should_read_and_write_nil
58
+ assert @cache.write('foo', nil)
59
+ assert_equal nil, @cache.read('foo')
60
+ end
61
+
62
+ def test_should_read_and_write_false
63
+ assert @cache.write('foo', false)
64
+ assert_equal false, @cache.read('foo')
65
+ end
66
+
67
+ def test_read_multi
68
+ @cache.write('foo', 'bar')
69
+ @cache.write('fu', 'baz')
70
+ @cache.write('fud', 'biz')
71
+ assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
72
+ end
73
+
74
+ def test_read_multi_with_expires
75
+ time = Time.now
76
+ @cache.write('foo', 'bar', :expires_in => 10)
77
+ @cache.write('fu', 'baz')
78
+ @cache.write('fud', 'biz')
79
+ Time.stubs(:now).returns(time + 11)
80
+ assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
81
+ end
82
+
83
+ def test_fetch_multi
84
+ @cache.write('foo', 'bar')
85
+ @cache.write('fud', 'biz')
86
+
87
+ values = @cache.fetch_multi('foo', 'fu', 'fud') {|value| value * 2 }
88
+
89
+ assert_equal(["bar", "fufu", "biz"], values)
90
+ assert_equal("fufu", @cache.read('fu'))
91
+ end
92
+
93
+ def test_multi_with_objects
94
+ foo = stub(:title => "FOO!", :cache_key => "foo")
95
+ bar = stub(:cache_key => "bar")
96
+
97
+ @cache.write('bar', "BAM!")
98
+
99
+ values = @cache.fetch_multi(foo, bar) {|object| object.title }
100
+ assert_equal(["FOO!", "BAM!"], values)
101
+ end
102
+
103
+ def test_read_and_write_compressed_small_data
104
+ @cache.write('foo', 'bar', :compress => true)
105
+ assert_equal 'bar', @cache.read('foo')
106
+ end
107
+
108
+ def test_read_and_write_compressed_large_data
109
+ @cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
110
+ assert_equal 'bar', @cache.read('foo')
111
+ end
112
+
113
+ def test_read_and_write_compressed_nil
114
+ @cache.write('foo', nil, :compress => true)
115
+ assert_nil @cache.read('foo')
116
+ end
117
+
118
+ def test_cache_key
119
+ obj = Object.new
120
+ def obj.cache_key
121
+ :foo
122
+ end
123
+ @cache.write(obj, "bar")
124
+ assert_equal "bar", @cache.read("foo")
125
+ end
126
+
127
+ def test_param_as_cache_key
128
+ obj = Object.new
129
+ def obj.to_param
130
+ "foo"
131
+ end
132
+ @cache.write(obj, "bar")
133
+ assert_equal "bar", @cache.read("foo")
134
+ end
135
+
136
+ def test_array_as_cache_key
137
+ @cache.write([:fu, "foo"], "bar")
138
+ assert_equal "bar", @cache.read("fu/foo")
139
+ end
140
+
141
+ def test_hash_as_cache_key
142
+ @cache.write({:foo => 1, :fu => 2}, "bar")
143
+ assert_equal "bar", @cache.read("foo=1/fu=2")
144
+ end
145
+
146
+ def test_keys_are_case_sensitive
147
+ @cache.write("foo", "bar")
148
+ assert_nil @cache.read("FOO")
149
+ end
150
+
151
+ def test_exist
152
+ @cache.write('foo', 'bar')
153
+ assert_equal true, @cache.exist?('foo')
154
+ assert_equal false, @cache.exist?('bar')
155
+ end
156
+
157
+ def test_nil_exist
158
+ @cache.write('foo', nil)
159
+ assert @cache.exist?('foo')
160
+ end
161
+
162
+ def test_delete
163
+ @cache.write('foo', 'bar')
164
+ assert @cache.exist?('foo')
165
+ assert @cache.delete('foo')
166
+ assert !@cache.exist?('foo')
167
+ end
168
+
169
+ def test_original_store_objects_should_not_be_immutable
170
+ bar = 'bar'
171
+ @cache.write('foo', bar)
172
+ assert_nothing_raised { bar.gsub!(/.*/, 'baz') }
173
+ end
174
+
175
+ def test_expires_in
176
+ time = Time.local(2008, 4, 24)
177
+ Time.stubs(:now).returns(time)
178
+
179
+ @cache.write('foo', 'bar')
180
+ assert_equal 'bar', @cache.read('foo')
181
+
182
+ Time.stubs(:now).returns(time + 30)
183
+ assert_equal 'bar', @cache.read('foo')
184
+
185
+ Time.stubs(:now).returns(time + 61)
186
+ assert_nil @cache.read('foo')
187
+ end
188
+
189
+ def test_race_condition_protection
190
+ time = Time.now
191
+ @cache.write('foo', 'bar', :expires_in => 60)
192
+ Time.stubs(:now).returns(time + 61)
193
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
194
+ assert_equal 'bar', @cache.read('foo')
195
+ "baz"
196
+ end
197
+ assert_equal "baz", result
198
+ end
199
+
200
+ def test_race_condition_protection_is_limited
201
+ time = Time.now
202
+ @cache.write('foo', 'bar', :expires_in => 60)
203
+ Time.stubs(:now).returns(time + 71)
204
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
205
+ assert_equal nil, @cache.read('foo')
206
+ "baz"
207
+ end
208
+ assert_equal "baz", result
209
+ end
210
+
211
+ def test_race_condition_protection_is_safe
212
+ time = Time.now
213
+ @cache.write('foo', 'bar', :expires_in => 60)
214
+ Time.stubs(:now).returns(time + 61)
215
+ begin
216
+ @cache.fetch('foo', :race_condition_ttl => 10) do
217
+ assert_equal 'bar', @cache.read('foo')
218
+ raise ArgumentError.new
219
+ end
220
+ rescue ArgumentError
221
+ end
222
+ assert_equal "bar", @cache.read('foo')
223
+ Time.stubs(:now).returns(time + 91)
224
+ assert_nil @cache.read('foo')
225
+ end
226
+
227
+ def test_crazy_key_characters
228
+ crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
229
+ assert @cache.write(crazy_key, "1", :raw => true)
230
+ assert_equal "1", @cache.read(crazy_key)
231
+ assert_equal "1", @cache.fetch(crazy_key)
232
+ assert @cache.delete(crazy_key)
233
+ assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
234
+ assert_equal 3, @cache.increment(crazy_key)
235
+ assert_equal 2, @cache.decrement(crazy_key)
236
+ end
237
+
238
+ def test_really_long_keys
239
+ key = ""
240
+ 900.times{key << "x"}
241
+ assert @cache.write(key, "bar")
242
+ assert_equal "bar", @cache.read(key)
243
+ assert_equal "bar", @cache.fetch(key)
244
+ assert_nil @cache.read("#{key}x")
245
+ assert_equal({key => "bar"}, @cache.read_multi(key))
246
+ assert @cache.delete(key)
247
+ end
248
+ end
@@ -0,0 +1,28 @@
1
+ module DependenciesTestHelpers
2
+ def with_loading(*from)
3
+ old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
4
+ this_dir = File.dirname(__FILE__)
5
+ parent_dir = File.dirname(this_dir)
6
+ path_copy = $LOAD_PATH.dup
7
+ $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
8
+ prior_autoload_paths = ActiveSupport::Dependencies.autoload_paths
9
+ ActiveSupport::Dependencies.autoload_paths = from.collect { |f| "#{this_dir}/#{f}" }
10
+ yield
11
+ ensure
12
+ $LOAD_PATH.replace(path_copy)
13
+ ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths
14
+ ActiveSupport::Dependencies.mechanism = old_mechanism
15
+ ActiveSupport::Dependencies.explicitly_unloadable_constants = []
16
+ ActiveSupport::Dependencies.clear
17
+ end
18
+
19
+ def with_autoloading_fixtures(&block)
20
+ with_loading 'autoloading_fixtures', &block
21
+ end
22
+
23
+ def remove_constants(*constants)
24
+ constants.each do |constant|
25
+ Object.send(:remove_const, constant) if Object.const_defined?(constant)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,34 @@
1
+ # https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
2
+ # The error is caused by character encodings that can't be compared with ASCII-8BIT regular expressions and by special
3
+ # characters like the umlaut in UTF-8.
4
+ module EncodedKeyCacheBehavior
5
+ Encoding.list.each do |encoding|
6
+ define_method "test_#{encoding.name.underscore}_encoded_values" do
7
+ key = "foo".force_encoding(encoding)
8
+ assert @cache.write(key, "1", :raw => true)
9
+ assert_equal "1", @cache.read(key)
10
+ assert_equal "1", @cache.fetch(key)
11
+ assert @cache.delete(key)
12
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
13
+ assert_equal 3, @cache.increment(key)
14
+ assert_equal 2, @cache.decrement(key)
15
+ end
16
+ end
17
+
18
+ def test_common_utf8_values
19
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
20
+ assert @cache.write(key, "1", :raw => true)
21
+ assert_equal "1", @cache.read(key)
22
+ assert_equal "1", @cache.fetch(key)
23
+ assert @cache.delete(key)
24
+ assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
25
+ assert_equal 3, @cache.increment(key)
26
+ assert_equal 2, @cache.decrement(key)
27
+ end
28
+
29
+ def test_retains_encoding
30
+ key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
31
+ assert @cache.write(key, "1", :raw => true)
32
+ assert_equal Encoding::UTF_8, key.encoding
33
+ end
34
+ end
@@ -0,0 +1,88 @@
1
+ module LocalCacheBehavior
2
+ def test_local_writes_are_persistent_on_the_remote_cache
3
+ retval = @cache.with_local_cache do
4
+ @cache.write('foo', 'bar')
5
+ end
6
+ assert retval
7
+ assert_equal 'bar', @cache.read('foo')
8
+ end
9
+
10
+ def test_clear_also_clears_local_cache
11
+ @cache.with_local_cache do
12
+ @cache.write('foo', 'bar')
13
+ @cache.clear
14
+ assert_nil @cache.read('foo')
15
+ end
16
+
17
+ assert_nil @cache.read('foo')
18
+ end
19
+
20
+ def test_local_cache_of_write
21
+ @cache.with_local_cache do
22
+ @cache.write('foo', 'bar')
23
+ @peek.delete('foo')
24
+ assert_equal 'bar', @cache.read('foo')
25
+ end
26
+ end
27
+
28
+ def test_local_cache_of_read
29
+ @cache.write('foo', 'bar')
30
+ @cache.with_local_cache do
31
+ assert_equal 'bar', @cache.read('foo')
32
+ end
33
+ end
34
+
35
+ def test_local_cache_of_write_nil
36
+ @cache.with_local_cache do
37
+ assert @cache.write('foo', nil)
38
+ assert_nil @cache.read('foo')
39
+ @peek.write('foo', 'bar')
40
+ assert_nil @cache.read('foo')
41
+ end
42
+ end
43
+
44
+ def test_local_cache_of_delete
45
+ @cache.with_local_cache do
46
+ @cache.write('foo', 'bar')
47
+ @cache.delete('foo')
48
+ assert_nil @cache.read('foo')
49
+ end
50
+ end
51
+
52
+ def test_local_cache_of_exist
53
+ @cache.with_local_cache do
54
+ @cache.write('foo', 'bar')
55
+ @peek.delete('foo')
56
+ assert @cache.exist?('foo')
57
+ end
58
+ end
59
+
60
+ def test_local_cache_of_increment
61
+ @cache.with_local_cache do
62
+ @cache.write('foo', 1, :raw => true)
63
+ @peek.write('foo', 2, :raw => true)
64
+ @cache.increment('foo')
65
+ assert_equal 3, @cache.read('foo')
66
+ end
67
+ end
68
+
69
+ def test_local_cache_of_decrement
70
+ @cache.with_local_cache do
71
+ @cache.write('foo', 1, :raw => true)
72
+ @peek.write('foo', 3, :raw => true)
73
+ @cache.decrement('foo')
74
+ assert_equal 2, @cache.read('foo')
75
+ end
76
+ end
77
+
78
+ def test_middleware
79
+ app = lambda { |env|
80
+ result = @cache.write('foo', 'bar')
81
+ assert_equal 'bar', @cache.read('foo') # make sure 'foo' was written
82
+ assert result
83
+ [200, {}, []]
84
+ }
85
+ app = @cache.middleware.new(app)
86
+ app.call({})
87
+ end
88
+ end
@@ -0,0 +1,90 @@
1
+ gem 'minitest' # make sure we get the gem, not stdlib
2
+ require 'minitest'
3
+ require "minitest/autorun"
4
+
5
+ require 'active_support'
6
+ require 'active_support/inflector'
7
+ require 'mocha/setup' # FIXME: stop using mocha
8
+
9
+ require 'cache_store_behavior'
10
+ require 'local_cache_behavior'
11
+ require 'cache_increment_decrement_behavior'
12
+ require 'encoded_key_cache_behavior'
13
+ require 'autoloading_cache_behavior'
14
+
15
+ class SpymemcachedStoreTest < ::Minitest::Test
16
+
17
+ def setup
18
+ @cache = @@cache ||= ActiveSupport::Cache.lookup_store(:spymemcached_store, :expires_in => 60)
19
+ @peek = @@peek ||= ActiveSupport::Cache.lookup_store(:spymemcached_store)
20
+ @data = @cache.instance_variable_get(:@client)
21
+ @cache.clear
22
+ @cache.silence!
23
+ @cache.logger = ActiveSupport::Logger.new("/dev/null")
24
+ end
25
+
26
+ include CacheStoreBehavior
27
+ include LocalCacheBehavior
28
+ include CacheIncrementDecrementBehavior
29
+ include EncodedKeyCacheBehavior
30
+ include AutoloadingCacheBehavior
31
+
32
+ def test_raw_values
33
+ cache = @peek
34
+ cache.write("foo", 2)
35
+ #spymemcached returns right type
36
+ assert_equal 2, cache.read("foo")
37
+ end
38
+
39
+ def test_raw_values_with_marshal
40
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :raw => true)
41
+ cache.clear
42
+ cache.write("foo", Marshal.dump([]))
43
+ assert_equal [], cache.read("foo")
44
+ end
45
+
46
+ def test_local_cache_raw_values
47
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :raw => true)
48
+ cache.clear
49
+ cache.with_local_cache do
50
+ cache.write("foo", '2')
51
+ assert_equal "2", cache.read("foo")
52
+ end
53
+ end
54
+
55
+ def test_local_cache_raw_values_with_marshal
56
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :raw => true)
57
+ cache.clear
58
+ cache.with_local_cache do
59
+ cache.write("foo", Marshal.dump([]))
60
+ assert_equal [], cache.read("foo")
61
+ end
62
+ end
63
+
64
+ def test_read_should_return_a_different_object_id_each_time_it_is_called
65
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :raw => true)
66
+ cache.write('foo', 'bar')
67
+ value = cache.read('foo')
68
+ assert_not_equal value.object_id, cache.read('foo').object_id
69
+ value << 'bingo'
70
+ assert_not_equal value, cache.read('foo')
71
+ end
72
+
73
+ def test_namespace
74
+ cache = ActiveSupport::Cache.lookup_store(:spymemcached_store, :namespace => "abc")
75
+ cache.write('foo', 'bar')
76
+ assert_equal 'bar', cache.read('foo')
77
+ assert_nil @cache.read('foo')
78
+ assert_equal 'bar', @cache.read('abc:foo')
79
+ end
80
+
81
+ def assert_not_equal(expected, result)
82
+ assert expected != result, "expect #{expected.inspect} not equal #{result.inspect}"
83
+ end
84
+
85
+ def assert_nothing_raised(&block)
86
+ block.call
87
+ rescue => e
88
+ fail("expected nothing raised, but caught #{e.class}: #{e.message}")
89
+ end
90
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spymemcached_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Xiao Li
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: spymemcached.jruby
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - '>='
23
+ - !ruby/object:Gem::Version
24
+ version: '0'
25
+ prerelease: false
26
+ type: :runtime
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: '1.7'
39
+ prerelease: false
40
+ type: :development
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ~>
51
+ - !ruby/object:Gem::Version
52
+ version: '10.0'
53
+ prerelease: false
54
+ type: :development
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ prerelease: false
68
+ type: :development
69
+ - !ruby/object:Gem::Dependency
70
+ name: mocha
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ prerelease: false
82
+ type: :development
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ prerelease: false
96
+ type: :development
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '5.1'
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ~>
107
+ - !ruby/object:Gem::Version
108
+ version: '5.1'
109
+ prerelease: false
110
+ type: :development
111
+ description: Rails 3 & 4 cache store for spymemcached.jruby.
112
+ email:
113
+ - swing1979@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - .gitignore
119
+ - .ruby-version
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - lib/active_support/cache/spymemcached_store.rb
125
+ - lib/spymemcached_store.rb
126
+ - lib/spymemcached_store/version.rb
127
+ - spymemcached_store.gemspec
128
+ - test/autoloading_cache_behavior.rb
129
+ - test/cache_increment_decrement_behavior.rb
130
+ - test/cache_store_behavior.rb
131
+ - test/dependencies_test_helpers.rb
132
+ - test/encoded_key_cache_behavior.rb
133
+ - test/local_cache_behavior.rb
134
+ - test/spymemcached_store_test.rb
135
+ homepage: https://github.com/ThoughtWorksStudios/spymemcached_store
136
+ licenses:
137
+ - MIT
138
+ metadata: {}
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - '>='
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubyforge_project:
155
+ rubygems_version: 2.1.9
156
+ signing_key:
157
+ specification_version: 4
158
+ summary: Rails 3 & 4 cache store for spymemcached.jruby.
159
+ test_files:
160
+ - test/autoloading_cache_behavior.rb
161
+ - test/cache_increment_decrement_behavior.rb
162
+ - test/cache_store_behavior.rb
163
+ - test/dependencies_test_helpers.rb
164
+ - test/encoded_key_cache_behavior.rb
165
+ - test/local_cache_behavior.rb
166
+ - test/spymemcached_store_test.rb