activesupport-cascadestore 0.0.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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/activesupport-cascadestore.gemspec +21 -0
- data/lib/active_support/cache/cascade_store.rb +97 -0
- data/lib/activesupport-cascadestore.rb +1 -0
- data/test/abstract_unit.rb +40 -0
- data/test/caching_test.rb +374 -0
- metadata +70 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ActiveSupport::Cache::CascadeStore
|
|
2
|
+
|
|
3
|
+
Hopefully this cache store is merged upstream into core
|
|
4
|
+
with [this pull request](https://github.com/rails/rails/pull/5263).
|
|
5
|
+
In the meantime, packaging this up as a gem for easy access.
|
|
6
|
+
|
|
7
|
+
A thread-safe cache store implementation that cascades
|
|
8
|
+
operations to a list of other cache stores. It is used to
|
|
9
|
+
provide fallback cache stores when primary stores become
|
|
10
|
+
unavailable, or to put lower latency stores in front of
|
|
11
|
+
other cache stores.
|
|
12
|
+
|
|
13
|
+
For example, to initialize a CascadeStore that
|
|
14
|
+
cascades through MemCacheStore, MemoryStore, and FileStore:
|
|
15
|
+
|
|
16
|
+
ActiveSupport::Cache.lookup_store(:cascade_store,
|
|
17
|
+
:stores => [
|
|
18
|
+
:mem_cache_store,
|
|
19
|
+
:memory_store,
|
|
20
|
+
:file_store
|
|
21
|
+
]
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
Cache operation behavior:
|
|
25
|
+
|
|
26
|
+
Read: returns first cache hit from :stores, nil if none found
|
|
27
|
+
|
|
28
|
+
Write/Delete: write/delete through to each cache store in
|
|
29
|
+
:stores
|
|
30
|
+
|
|
31
|
+
Increment/Decrement: increment/decrement each store, returning
|
|
32
|
+
the new number if any stores was successfully
|
|
33
|
+
incremented/decremented, nil otherwise
|
|
34
|
+
|
|
35
|
+
### Development
|
|
36
|
+
|
|
37
|
+
To run tests
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
ruby -Itest test/caching_test.rb
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### License
|
|
44
|
+
|
|
45
|
+
Same license as [Rails](http://github.com/rails/rails)
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |s|
|
|
5
|
+
s.name = "activesupport-cascadestore"
|
|
6
|
+
s.version = "0.0.1"
|
|
7
|
+
s.authors = ["Jerry Cheung"]
|
|
8
|
+
s.email = ["jch@whatcodecraves.com"]
|
|
9
|
+
s.homepage = "http://github.com/jch/activesupport-cascadestore"
|
|
10
|
+
s.summary = %q{write-through cache store that allows you to chain multiple cache stores together}
|
|
11
|
+
s.description = %q{write-through cache store that allows you to chain multiple cache stores together}
|
|
12
|
+
|
|
13
|
+
s.rubyforge_project = "activesupport-cascadestore"
|
|
14
|
+
|
|
15
|
+
s.files = `git ls-files`.split("\n")
|
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
18
|
+
s.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
s.add_runtime_dependency "activesupport"
|
|
21
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'monitor'
|
|
2
|
+
|
|
3
|
+
module ActiveSupport
|
|
4
|
+
module Cache
|
|
5
|
+
# A thread-safe cache store implementation that cascades
|
|
6
|
+
# operations to a list of other cache stores. It is used to
|
|
7
|
+
# provide fallback cache stores when primary stores become
|
|
8
|
+
# unavailable. For example, to initialize a CascadeStore that
|
|
9
|
+
# cascades through MemCacheStore, MemoryStore, and FileStore:
|
|
10
|
+
#
|
|
11
|
+
# ActiveSupport::Cache.lookup_store(:cascade_store,
|
|
12
|
+
# :stores => [
|
|
13
|
+
# :mem_cache_store,
|
|
14
|
+
# :memory_store,
|
|
15
|
+
# :file_store
|
|
16
|
+
# ]
|
|
17
|
+
# })
|
|
18
|
+
#
|
|
19
|
+
# Cache operation behavior:
|
|
20
|
+
#
|
|
21
|
+
# Read: returns first cache hit from :stores, nil if none found
|
|
22
|
+
#
|
|
23
|
+
# Write/Delete: write/delete through to each cache store in
|
|
24
|
+
# :stores
|
|
25
|
+
#
|
|
26
|
+
# Increment/Decrement: increment/decrement each store, returning
|
|
27
|
+
# the new number if any stores was successfully
|
|
28
|
+
# incremented/decremented, nil otherwise
|
|
29
|
+
class CascadeStore < Store
|
|
30
|
+
attr_reader :stores
|
|
31
|
+
|
|
32
|
+
# Initialize a CascadeStore with +options[:stores]+, an array of
|
|
33
|
+
# options to initialize other ActiveSupport::Cache::Store
|
|
34
|
+
# implementations. If options is a symbol, top level
|
|
35
|
+
# CascadeStore options are used for cascaded stores. If options
|
|
36
|
+
# is an array, they are passed on unchanged.
|
|
37
|
+
def initialize(options = nil, &blk)
|
|
38
|
+
options ||= {}
|
|
39
|
+
super(options)
|
|
40
|
+
@monitor = Monitor.new
|
|
41
|
+
store_options = options.delete(:stores) || []
|
|
42
|
+
@stores = store_options.map do |o|
|
|
43
|
+
o = o.is_a?(Symbol) ? [o, options] : o
|
|
44
|
+
ActiveSupport::Cache.lookup_store(*o)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def increment(name, amount = 1, options = nil)
|
|
49
|
+
nums = cascade(:increment, name, amount, options)
|
|
50
|
+
nums.detect {|n| !n.nil?}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def decrement(name, amount = 1, options = nil)
|
|
54
|
+
nums = cascade(:decrement, name, amount, options)
|
|
55
|
+
nums.detect {|n| !n.nil?}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def delete_matched(matcher, options = nil)
|
|
59
|
+
cascade(:delete_matched, matcher, options)
|
|
60
|
+
nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
protected
|
|
64
|
+
def synchronize(&block) # :nodoc:
|
|
65
|
+
@monitor.synchronize(&block)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def cascade(method, *args) # :nodoc:
|
|
69
|
+
synchronize do
|
|
70
|
+
@stores.map do |store|
|
|
71
|
+
store.send(method, *args) rescue nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def read_entry(key, options) # :nodoc:
|
|
77
|
+
entry = nil
|
|
78
|
+
synchronize do
|
|
79
|
+
@stores.detect do |store|
|
|
80
|
+
entry = store.send(:read_entry, key, options)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
entry
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def write_entry(key, entry, options) # :nodoc:
|
|
87
|
+
cascade(:write_entry, key, entry, options)
|
|
88
|
+
true
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def delete_entry(key, options) # :nodoc:
|
|
92
|
+
cascade(:delete_entry, key, options)
|
|
93
|
+
true
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "active_support/cache"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
ORIG_ARGV = ARGV.dup
|
|
2
|
+
|
|
3
|
+
# begin
|
|
4
|
+
# old, $VERBOSE = $VERBOSE, nil
|
|
5
|
+
# require File.expand_path('../../../load_paths', __FILE__)
|
|
6
|
+
# ensure
|
|
7
|
+
# $VERBOSE = old
|
|
8
|
+
# end
|
|
9
|
+
|
|
10
|
+
lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
|
11
|
+
$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
|
|
12
|
+
|
|
13
|
+
require 'active_support/core_ext/kernel/reporting'
|
|
14
|
+
require 'active_support/core_ext/string/encoding'
|
|
15
|
+
|
|
16
|
+
silence_warnings do
|
|
17
|
+
Encoding.default_internal = "UTF-8"
|
|
18
|
+
Encoding.default_external = "UTF-8"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
require 'minitest/autorun'
|
|
22
|
+
# require 'empty_bool'
|
|
23
|
+
|
|
24
|
+
silence_warnings { require 'mocha' }
|
|
25
|
+
|
|
26
|
+
ENV['NO_RELOAD'] = '1'
|
|
27
|
+
require 'active_support'
|
|
28
|
+
|
|
29
|
+
def uses_memcached(test_name)
|
|
30
|
+
require 'memcache'
|
|
31
|
+
begin
|
|
32
|
+
MemCache.new('localhost:11211').stats
|
|
33
|
+
yield
|
|
34
|
+
rescue MemCache::MemCacheError
|
|
35
|
+
$stderr.puts "Skipping #{test_name} tests. Start memcached and try again."
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Show backtraces for deprecated behavior for quicker cleanup.
|
|
40
|
+
ActiveSupport::Deprecation.debug = true
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
require 'abstract_unit'
|
|
3
|
+
require 'active_support/cache'
|
|
4
|
+
|
|
5
|
+
# Tests the base functionality that should be identical across all cache stores.
|
|
6
|
+
module CacheStoreBehavior
|
|
7
|
+
def test_should_read_and_write_strings
|
|
8
|
+
assert @cache.write('foo', 'bar')
|
|
9
|
+
assert_equal 'bar', @cache.read('foo')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_should_overwrite
|
|
13
|
+
@cache.write('foo', 'bar')
|
|
14
|
+
@cache.write('foo', 'baz')
|
|
15
|
+
assert_equal 'baz', @cache.read('foo')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_fetch_without_cache_miss
|
|
19
|
+
@cache.write('foo', 'bar')
|
|
20
|
+
@cache.expects(:write).never
|
|
21
|
+
assert_equal 'bar', @cache.fetch('foo') { 'baz' }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_fetch_with_cache_miss
|
|
25
|
+
@cache.expects(:write).with('foo', 'baz', @cache.options)
|
|
26
|
+
assert_equal 'baz', @cache.fetch('foo') { 'baz' }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_fetch_with_forced_cache_miss
|
|
30
|
+
@cache.write('foo', 'bar')
|
|
31
|
+
@cache.expects(:read).never
|
|
32
|
+
@cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true))
|
|
33
|
+
@cache.fetch('foo', :force => true) { 'bar' }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def test_fetch_with_cached_nil
|
|
37
|
+
@cache.write('foo', nil)
|
|
38
|
+
@cache.expects(:write).never
|
|
39
|
+
assert_nil @cache.fetch('foo') { 'baz' }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_should_read_and_write_hash
|
|
43
|
+
assert @cache.write('foo', {:a => "b"})
|
|
44
|
+
assert_equal({:a => "b"}, @cache.read('foo'))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_should_read_and_write_integer
|
|
48
|
+
assert @cache.write('foo', 1)
|
|
49
|
+
assert_equal 1, @cache.read('foo')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def test_should_read_and_write_nil
|
|
53
|
+
assert @cache.write('foo', nil)
|
|
54
|
+
assert_equal nil, @cache.read('foo')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_should_read_and_write_false
|
|
58
|
+
assert @cache.write('foo', false)
|
|
59
|
+
assert_equal false, @cache.read('foo')
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_read_multi
|
|
63
|
+
@cache.write('foo', 'bar')
|
|
64
|
+
@cache.write('fu', 'baz')
|
|
65
|
+
@cache.write('fud', 'biz')
|
|
66
|
+
assert_equal({"foo" => "bar", "fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def test_read_multi_with_expires
|
|
70
|
+
@cache.write('foo', 'bar', :expires_in => 0.001)
|
|
71
|
+
@cache.write('fu', 'baz')
|
|
72
|
+
@cache.write('fud', 'biz')
|
|
73
|
+
sleep(0.002)
|
|
74
|
+
assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_read_and_write_compressed_small_data
|
|
78
|
+
@cache.write('foo', 'bar', :compress => true)
|
|
79
|
+
raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
|
|
80
|
+
assert_equal 'bar', @cache.read('foo')
|
|
81
|
+
assert_equal 'bar', Marshal.load(raw_value)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_read_and_write_compressed_large_data
|
|
85
|
+
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
|
|
86
|
+
raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
|
|
87
|
+
assert_equal 'bar', @cache.read('foo')
|
|
88
|
+
assert_equal 'bar', Marshal.load(Zlib::Inflate.inflate(raw_value))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def test_read_and_write_compressed_nil
|
|
92
|
+
@cache.write('foo', nil, :compress => true)
|
|
93
|
+
assert_nil @cache.read('foo')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def test_cache_key
|
|
97
|
+
obj = Object.new
|
|
98
|
+
def obj.cache_key
|
|
99
|
+
:foo
|
|
100
|
+
end
|
|
101
|
+
@cache.write(obj, "bar")
|
|
102
|
+
assert_equal "bar", @cache.read("foo")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def test_param_as_cache_key
|
|
106
|
+
obj = Object.new
|
|
107
|
+
def obj.to_param
|
|
108
|
+
"foo"
|
|
109
|
+
end
|
|
110
|
+
@cache.write(obj, "bar")
|
|
111
|
+
assert_equal "bar", @cache.read("foo")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_array_as_cache_key
|
|
115
|
+
@cache.write([:fu, "foo"], "bar")
|
|
116
|
+
assert_equal "bar", @cache.read("fu/foo")
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def test_hash_as_cache_key
|
|
120
|
+
@cache.write({:foo => 1, :fu => 2}, "bar")
|
|
121
|
+
assert_equal "bar", @cache.read("foo=1/fu=2")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def test_keys_are_case_sensitive
|
|
125
|
+
@cache.write("foo", "bar")
|
|
126
|
+
assert_nil @cache.read("FOO")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def test_exist
|
|
130
|
+
@cache.write('foo', 'bar')
|
|
131
|
+
assert @cache.exist?('foo')
|
|
132
|
+
assert !@cache.exist?('bar')
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_nil_exist
|
|
136
|
+
@cache.write('foo', nil)
|
|
137
|
+
assert @cache.exist?('foo')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def test_delete
|
|
141
|
+
@cache.write('foo', 'bar')
|
|
142
|
+
assert @cache.exist?('foo')
|
|
143
|
+
assert @cache.delete('foo')
|
|
144
|
+
assert !@cache.exist?('foo')
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_read_should_return_a_different_object_id_each_time_it_is_called
|
|
148
|
+
@cache.write('foo', 'bar')
|
|
149
|
+
assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id
|
|
150
|
+
value = @cache.read('foo')
|
|
151
|
+
value << 'bingo'
|
|
152
|
+
assert_not_equal value, @cache.read('foo')
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def test_original_store_objects_should_not_be_immutable
|
|
156
|
+
bar = 'bar'
|
|
157
|
+
@cache.write('foo', bar)
|
|
158
|
+
assert_nothing_raised { bar.gsub!(/.*/, 'baz') }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def test_expires_in
|
|
162
|
+
time = Time.local(2008, 4, 24)
|
|
163
|
+
Time.stubs(:now).returns(time)
|
|
164
|
+
|
|
165
|
+
@cache.write('foo', 'bar')
|
|
166
|
+
assert_equal 'bar', @cache.read('foo')
|
|
167
|
+
|
|
168
|
+
Time.stubs(:now).returns(time + 30)
|
|
169
|
+
assert_equal 'bar', @cache.read('foo')
|
|
170
|
+
|
|
171
|
+
Time.stubs(:now).returns(time + 61)
|
|
172
|
+
assert_nil @cache.read('foo')
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def test_race_condition_protection
|
|
176
|
+
time = Time.now
|
|
177
|
+
@cache.write('foo', 'bar', :expires_in => 60)
|
|
178
|
+
Time.stubs(:now).returns(time + 61)
|
|
179
|
+
result = @cache.fetch('foo', :race_condition_ttl => 10) do
|
|
180
|
+
assert_equal 'bar', @cache.read('foo')
|
|
181
|
+
"baz"
|
|
182
|
+
end
|
|
183
|
+
assert_equal "baz", result
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def test_race_condition_protection_is_limited
|
|
187
|
+
time = Time.now
|
|
188
|
+
@cache.write('foo', 'bar', :expires_in => 60)
|
|
189
|
+
Time.stubs(:now).returns(time + 71)
|
|
190
|
+
result = @cache.fetch('foo', :race_condition_ttl => 10) do
|
|
191
|
+
assert_equal nil, @cache.read('foo')
|
|
192
|
+
"baz"
|
|
193
|
+
end
|
|
194
|
+
assert_equal "baz", result
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def test_race_condition_protection_is_safe
|
|
198
|
+
time = Time.now
|
|
199
|
+
@cache.write('foo', 'bar', :expires_in => 60)
|
|
200
|
+
Time.stubs(:now).returns(time + 61)
|
|
201
|
+
begin
|
|
202
|
+
@cache.fetch('foo', :race_condition_ttl => 10) do
|
|
203
|
+
assert_equal 'bar', @cache.read('foo')
|
|
204
|
+
raise ArgumentError.new
|
|
205
|
+
end
|
|
206
|
+
rescue ArgumentError
|
|
207
|
+
end
|
|
208
|
+
assert_equal "bar", @cache.read('foo')
|
|
209
|
+
Time.stubs(:now).returns(time + 71)
|
|
210
|
+
assert_nil @cache.read('foo')
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def test_crazy_key_characters
|
|
214
|
+
crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
|
|
215
|
+
assert @cache.write(crazy_key, "1", :raw => true)
|
|
216
|
+
assert_equal "1", @cache.read(crazy_key)
|
|
217
|
+
assert_equal "1", @cache.fetch(crazy_key)
|
|
218
|
+
assert @cache.delete(crazy_key)
|
|
219
|
+
assert_equal "2", @cache.fetch(crazy_key, :raw => true) { "2" }
|
|
220
|
+
assert_equal 3, @cache.increment(crazy_key)
|
|
221
|
+
assert_equal 2, @cache.decrement(crazy_key)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def test_really_long_keys
|
|
225
|
+
key = ""
|
|
226
|
+
900.times{key << "x"}
|
|
227
|
+
assert @cache.write(key, "bar")
|
|
228
|
+
assert_equal "bar", @cache.read(key)
|
|
229
|
+
assert_equal "bar", @cache.fetch(key)
|
|
230
|
+
assert_nil @cache.read("#{key}x")
|
|
231
|
+
assert_equal({key => "bar"}, @cache.read_multi(key))
|
|
232
|
+
assert @cache.delete(key)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
|
|
237
|
+
# The error is caused by charcter encodings that can't be compared with ASCII-8BIT regular expressions and by special
|
|
238
|
+
# characters like the umlaut in UTF-8.
|
|
239
|
+
module EncodedKeyCacheBehavior
|
|
240
|
+
Encoding.list.each do |encoding|
|
|
241
|
+
define_method "test_#{encoding.name.underscore}_encoded_values" do
|
|
242
|
+
key = "foo".force_encoding(encoding)
|
|
243
|
+
assert @cache.write(key, "1", :raw => true)
|
|
244
|
+
assert_equal "1", @cache.read(key)
|
|
245
|
+
assert_equal "1", @cache.fetch(key)
|
|
246
|
+
assert @cache.delete(key)
|
|
247
|
+
assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
|
|
248
|
+
assert_equal 3, @cache.increment(key)
|
|
249
|
+
assert_equal 2, @cache.decrement(key)
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def test_common_utf8_values
|
|
254
|
+
key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
|
|
255
|
+
assert @cache.write(key, "1", :raw => true)
|
|
256
|
+
assert_equal "1", @cache.read(key)
|
|
257
|
+
assert_equal "1", @cache.fetch(key)
|
|
258
|
+
assert @cache.delete(key)
|
|
259
|
+
assert_equal "2", @cache.fetch(key, :raw => true) { "2" }
|
|
260
|
+
assert_equal 3, @cache.increment(key)
|
|
261
|
+
assert_equal 2, @cache.decrement(key)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def test_retains_encoding
|
|
265
|
+
key = "\xC3\xBCmlaut".force_encoding(Encoding::UTF_8)
|
|
266
|
+
assert @cache.write(key, "1", :raw => true)
|
|
267
|
+
assert_equal Encoding::UTF_8, key.encoding
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
module CacheDeleteMatchedBehavior
|
|
272
|
+
def test_delete_matched
|
|
273
|
+
@cache.write("foo", "bar")
|
|
274
|
+
@cache.write("fu", "baz")
|
|
275
|
+
@cache.write("foo/bar", "baz")
|
|
276
|
+
@cache.write("fu/baz", "bar")
|
|
277
|
+
@cache.delete_matched(/oo/)
|
|
278
|
+
assert !@cache.exist?("foo")
|
|
279
|
+
assert @cache.exist?("fu")
|
|
280
|
+
assert !@cache.exist?("foo/bar")
|
|
281
|
+
assert @cache.exist?("fu/baz")
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
module CacheIncrementDecrementBehavior
|
|
286
|
+
def test_increment
|
|
287
|
+
@cache.write('foo', 1, :raw => true)
|
|
288
|
+
assert_equal 1, @cache.read('foo').to_i
|
|
289
|
+
assert_equal 2, @cache.increment('foo')
|
|
290
|
+
assert_equal 2, @cache.read('foo').to_i
|
|
291
|
+
assert_equal 3, @cache.increment('foo')
|
|
292
|
+
assert_equal 3, @cache.read('foo').to_i
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def test_decrement
|
|
296
|
+
@cache.write('foo', 3, :raw => true)
|
|
297
|
+
assert_equal 3, @cache.read('foo').to_i
|
|
298
|
+
assert_equal 2, @cache.decrement('foo')
|
|
299
|
+
assert_equal 2, @cache.read('foo').to_i
|
|
300
|
+
assert_equal 1, @cache.decrement('foo')
|
|
301
|
+
assert_equal 1, @cache.read('foo').to_i
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
class CascadeStoreTest < ActiveSupport::TestCase
|
|
306
|
+
def setup
|
|
307
|
+
@cache = ActiveSupport::Cache.lookup_store(:cascade_store, {
|
|
308
|
+
:expires_in => 60,
|
|
309
|
+
:stores => [
|
|
310
|
+
:memory_store,
|
|
311
|
+
[:memory_store, :expires_in => 60]
|
|
312
|
+
]
|
|
313
|
+
})
|
|
314
|
+
@store1 = @cache.stores[0]
|
|
315
|
+
@store2 = @cache.stores[1]
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
include CacheStoreBehavior
|
|
319
|
+
include CacheIncrementDecrementBehavior
|
|
320
|
+
include CacheDeleteMatchedBehavior
|
|
321
|
+
include EncodedKeyCacheBehavior
|
|
322
|
+
|
|
323
|
+
def test_default_child_store_options
|
|
324
|
+
assert_equal @store1.options[:expires_in], 60
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def test_empty_store_cache_miss
|
|
328
|
+
cache = ActiveSupport::Cache.lookup_store(:cascade_store)
|
|
329
|
+
assert cache.write('foo', 'bar')
|
|
330
|
+
assert cache.fetch('foo').nil?
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def test_cascade_write
|
|
334
|
+
@cache.write('foo', 'bar')
|
|
335
|
+
assert_equal @store1.read('foo'), 'bar'
|
|
336
|
+
assert_equal @store2.read('foo'), 'bar'
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def test_cascade_read_returns_first_hit
|
|
340
|
+
@store1.write('foo', 'bar')
|
|
341
|
+
@store2.expects(:read_entry).never
|
|
342
|
+
assert_equal @cache.read('foo'), 'bar'
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def test_cascade_read_fallback
|
|
346
|
+
@store1.delete('foo')
|
|
347
|
+
@store2.write('foo', 'bar')
|
|
348
|
+
assert_equal @cache.read('foo'), 'bar'
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def test_cascade_read_not_found
|
|
352
|
+
assert_equal @cache.read('foo'), nil
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def test_cascade_delete
|
|
356
|
+
@store1.write('foo', 'bar')
|
|
357
|
+
@store2.write('foo', 'bar')
|
|
358
|
+
@cache.delete('foo')
|
|
359
|
+
assert_equal @store1.read('foo'), nil
|
|
360
|
+
assert_equal @store2.read('foo'), nil
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def test_cascade_increment_partial_returns_num
|
|
364
|
+
@store2.write('foo', 0)
|
|
365
|
+
assert_equal @cache.increment('foo', 1), 1
|
|
366
|
+
assert_equal @cache.read('foo'), 1
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def test_cascade_decrement_partial_returns_num
|
|
370
|
+
@store2.write('foo', 1)
|
|
371
|
+
assert_equal @cache.decrement('foo', 1), 0
|
|
372
|
+
assert_equal @cache.read('foo'), 0
|
|
373
|
+
end
|
|
374
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: activesupport-cascadestore
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Jerry Cheung
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-03-06 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: activesupport
|
|
16
|
+
requirement: &70233264692760 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *70233264692760
|
|
25
|
+
description: write-through cache store that allows you to chain multiple cache stores
|
|
26
|
+
together
|
|
27
|
+
email:
|
|
28
|
+
- jch@whatcodecraves.com
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- .gitignore
|
|
34
|
+
- Gemfile
|
|
35
|
+
- README.md
|
|
36
|
+
- Rakefile
|
|
37
|
+
- activesupport-cascadestore.gemspec
|
|
38
|
+
- lib/active_support/cache/cascade_store.rb
|
|
39
|
+
- lib/activesupport-cascadestore.rb
|
|
40
|
+
- test/abstract_unit.rb
|
|
41
|
+
- test/caching_test.rb
|
|
42
|
+
homepage: http://github.com/jch/activesupport-cascadestore
|
|
43
|
+
licenses: []
|
|
44
|
+
post_install_message:
|
|
45
|
+
rdoc_options: []
|
|
46
|
+
require_paths:
|
|
47
|
+
- lib
|
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
|
+
none: false
|
|
50
|
+
requirements:
|
|
51
|
+
- - ! '>='
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
none: false
|
|
56
|
+
requirements:
|
|
57
|
+
- - ! '>='
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0'
|
|
60
|
+
requirements: []
|
|
61
|
+
rubyforge_project: activesupport-cascadestore
|
|
62
|
+
rubygems_version: 1.8.10
|
|
63
|
+
signing_key:
|
|
64
|
+
specification_version: 3
|
|
65
|
+
summary: write-through cache store that allows you to chain multiple cache stores
|
|
66
|
+
together
|
|
67
|
+
test_files:
|
|
68
|
+
- test/abstract_unit.rb
|
|
69
|
+
- test/caching_test.rb
|
|
70
|
+
has_rdoc:
|