cached_record 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,351 @@
1
+ require File.expand_path("../../test_helper", __FILE__)
2
+
3
+ module Unit
4
+ class TestCache < MiniTest::Test
5
+
6
+ describe CachedRecord::Cache do
7
+ after do
8
+ CachedRecord::Cache.instance_variable_set :@stores, nil
9
+ end
10
+
11
+ describe ".memcached" do
12
+ after do
13
+ Dalli::Client.unstub :new
14
+ end
15
+ it "initiates a Dalli::Client instance" do
16
+ Dalli::Client.expects(:new).with "127.0.0.1:11211", {}
17
+ CachedRecord::Cache.memcached :host => "127.0.0.1"
18
+ end
19
+ it "memoizes a Dalli::Client instance" do
20
+ client = mock
21
+ Dalli::Client.expects(:new).returns client
22
+ CachedRecord::Cache.memcached
23
+ assert_equal CachedRecord::Cache.memcached.object_id, client.object_id
24
+ assert_equal CachedRecord::Cache.memcached.object_id, client.object_id
25
+ end
26
+ end
27
+
28
+ describe ".redis" do
29
+ after do
30
+ Redis.unstub :new
31
+ end
32
+ it "initiates a Redis instance" do
33
+ Redis.expects(:new).with :host => "127.0.0.1"
34
+ CachedRecord::Cache.redis :host => "127.0.0.1"
35
+ end
36
+ it "memoizes a Redis instance" do
37
+ client = mock
38
+ Redis.expects(:new).returns client
39
+ CachedRecord::Cache.redis
40
+ assert_equal CachedRecord::Cache.redis.object_id, client.object_id
41
+ assert_equal CachedRecord::Cache.redis.object_id, client.object_id
42
+ end
43
+ end
44
+
45
+ describe "cache store" do
46
+ describe ".valid_store?" do
47
+ it "returns whether the passed argument is a valid cache store" do
48
+ assert_equal true, CachedRecord::Cache.valid_store?("redis")
49
+ assert_equal true, CachedRecord::Cache.valid_store?("memcached")
50
+ assert_equal true, CachedRecord::Cache.valid_store?(:redis)
51
+ assert_equal true, CachedRecord::Cache.valid_store?(:memcached)
52
+ assert_equal false, CachedRecord::Cache.valid_store?("foo")
53
+ assert_equal false, CachedRecord::Cache.valid_store?(:foo)
54
+ end
55
+ end
56
+ describe ".store" do
57
+ describe "with as_cache[:store]" do
58
+ describe "when valid" do
59
+ it "returns the specified cache client" do
60
+ klass = mock
61
+ klass.expects(:as_cache).returns({:store => :redis})
62
+ CachedRecord::Cache.expects(:send).with(:redis)
63
+ CachedRecord::Cache.store(klass)
64
+ end
65
+ end
66
+ describe "when invalid" do
67
+ it "returns the specified cache client" do
68
+ klass = mock
69
+ klass.expects(:as_cache).returns({:store => :foo})
70
+ assert_raises CachedRecord::Cache::Error do
71
+ CachedRecord::Cache.store(klass)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ describe "without as_cache[:store]" do
77
+ before do
78
+ @klass = mock
79
+ @klass.stubs(:as_cache).returns({})
80
+ end
81
+ describe "with zero cache stores" do
82
+ it "raises an error" do
83
+ CachedRecord::Cache.instance_variable_set :@stores, {}
84
+ assert_raises CachedRecord::Cache::Error do
85
+ CachedRecord::Cache.store(@klass)
86
+ end
87
+ end
88
+ end
89
+ describe "with one cache store" do
90
+ it "returns the cache store" do
91
+ CachedRecord::Cache.instance_variable_set :@stores, {:memcached => mock}
92
+ CachedRecord::Cache.store(@klass)
93
+ end
94
+ end
95
+ describe "with multiple cache store" do
96
+ it "raises an error" do
97
+ CachedRecord::Cache.instance_variable_set :@stores, {:memcached => mock, :redis => mock}
98
+ assert_raises CachedRecord::Cache::Error do
99
+ CachedRecord::Cache.store(@klass)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ describe ".cache" do
106
+ it "returns a Hash" do
107
+ assert_equal({Dalli::Client => {}, Redis => {}}, CachedRecord::Cache.cache)
108
+ end
109
+ end
110
+ describe ".clear!" do
111
+ it "returns a Hash" do
112
+ CachedRecord::Cache.instance_variable_set :@cache, {:foo => :bar}
113
+ assert_equal({:foo => :bar}, CachedRecord::Cache.cache)
114
+
115
+ CachedRecord::Cache.clear!
116
+ assert_nil CachedRecord::Cache.instance_variable_get(:@cache)
117
+
118
+ assert_equal({Dalli::Client => {}, Redis => {}}, CachedRecord::Cache.cache)
119
+ end
120
+ end
121
+ describe ".get" do
122
+ before do
123
+ @klass = mock
124
+ @klass.stubs(:cache_key).returns("mock.123")
125
+ end
126
+ describe "without memoization" do
127
+ before do
128
+ @klass.stubs(:as_cache).returns({})
129
+ end
130
+ describe "with cache entry" do
131
+ it "returns a cached instance" do
132
+ store = mock
133
+ store.expects(:get).with("mock.123").returns('{"id":123}')
134
+ CachedRecord::Cache.expects(:store).with(@klass).returns(store)
135
+
136
+ cached_instance = mock
137
+ @klass.expects(:load_cache_json).with({"id" => 123}).returns cached_instance
138
+
139
+ assert_equal cached_instance, CachedRecord::Cache.get(@klass, 123)
140
+ end
141
+ end
142
+ end
143
+ describe "with memoization" do
144
+ before do
145
+ @klass.stubs(:as_cache).returns({:store => :redis, :memoize => true})
146
+ end
147
+ describe "without cache entry" do
148
+ it "returns nil" do
149
+ store = mock
150
+ store.expects(:get).with("mock.123").returns(nil)
151
+ CachedRecord::Cache.expects(:store).with(@klass).returns(store)
152
+ assert_equal nil, CachedRecord::Cache.get(@klass, 123)
153
+ end
154
+ end
155
+ describe "with cache entry" do
156
+ before do
157
+ @store = mock
158
+ @store.stubs(:get).with("mock.123").returns('{"id":123,"foo":"b@r"}@123456')
159
+ @store.stubs(:class).returns(Redis)
160
+ CachedRecord::Cache.stubs(:store).with(@klass).returns(@store)
161
+ end
162
+ describe "without memoized instance" do
163
+ it "returns a memoized instance" do
164
+ hash = mock
165
+ instance = mock
166
+
167
+ CachedRecord::Cache.expects(:cache).returns(Redis => hash)
168
+ hash.expects(:[]).with("mock.123").returns(nil)
169
+ hash.expects(:[]=).with("mock.123", :instance => instance, :epoch_time => 123456)
170
+ @klass.expects(:load_cache_json).with({"id" => 123,"foo" => "b@r"}).returns instance
171
+
172
+ assert_equal instance, CachedRecord::Cache.get(@klass, 123)
173
+
174
+ cache = {Redis => {"mock.123" => {:instance => instance, :epoch_time => 123456}}}
175
+ CachedRecord::Cache.expects(:cache).returns(cache)
176
+ @klass.expects(:load_cache_json).never
177
+
178
+ assert_equal instance, CachedRecord::Cache.get(@klass, 123)
179
+ end
180
+ end
181
+ describe "with memoized instance" do
182
+ describe "outdated instance" do
183
+ it "returns a new memoized instance" do
184
+ hash = mock
185
+ outdated_instance = mock
186
+ instance = mock
187
+
188
+ CachedRecord::Cache.expects(:cache).returns(Redis => hash)
189
+ hash.expects(:[]).with("mock.123").returns({:instance => outdated_instance, :epoch_time => 12345})
190
+ hash.expects(:[]=).with("mock.123", :instance => instance, :epoch_time => 123456)
191
+ @klass.expects(:load_cache_json).with({"id" => 123,"foo" => "b@r"}).returns instance
192
+
193
+ assert_equal instance, CachedRecord::Cache.get(@klass, 123)
194
+
195
+ cache = {Redis => {"mock.123" => {:instance => instance, :epoch_time => 123456}}}
196
+ CachedRecord::Cache.expects(:cache).returns(cache)
197
+ @klass.expects(:load_cache_json).never
198
+
199
+ assert_equal instance, CachedRecord::Cache.get(@klass, 123)
200
+ end
201
+ end
202
+ describe "uptodate instance" do
203
+ it "returns the memoized instance" do
204
+ hash = mock
205
+ instance = mock
206
+
207
+ CachedRecord::Cache.expects(:cache).returns(Redis => hash)
208
+ hash.expects(:[]).with("mock.123").returns({:instance => instance, :epoch_time => 123456})
209
+ @klass.expects(:load_cache_json).never
210
+
211
+ assert_equal instance, CachedRecord::Cache.get(@klass, 123)
212
+ end
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+ describe ".set" do
219
+ before do
220
+ id = 123
221
+
222
+ @klass = mock
223
+ @klass.stubs(:cache_key).with(id).returns("mock.123")
224
+
225
+ @instance = mock
226
+ @instance.stubs(:id).returns(id)
227
+ @instance.stubs(:class).returns(@klass)
228
+ @instance.stubs(:to_cache_json).returns('{"id":123}')
229
+
230
+ @store = mock
231
+ CachedRecord::Cache.stubs(:store).returns(@store)
232
+ end
233
+ describe "without memoization" do
234
+ before do
235
+ @klass.stubs(:as_cache).returns({})
236
+ end
237
+ it "stores cache JSON" do
238
+ @store.expects(:set).with("mock.123", '{"id":123}', nil)
239
+ CachedRecord::Cache.set @instance
240
+ end
241
+ end
242
+ describe "with memoization" do
243
+ before do
244
+ @klass.stubs(:as_cache).returns({:memoize => true})
245
+ end
246
+ it "stores cache JSON" do
247
+ Time.any_instance.expects(:to_i).returns(123456789)
248
+ @store.expects(:set).with("mock.123", '{"id":123}@123456789', nil)
249
+ CachedRecord::Cache.set @instance
250
+ end
251
+ end
252
+ end
253
+ describe ".expire" do
254
+ before do
255
+ id = 123
256
+
257
+ @klass = mock
258
+ @klass.stubs(:cache_key).with(id).returns("mock.123")
259
+
260
+ @instance = mock
261
+ @instance.stubs(:id).returns(id)
262
+ @instance.stubs(:class).returns(@klass)
263
+
264
+ @store = mock
265
+ CachedRecord::Cache.stubs(:store).returns(@store)
266
+ end
267
+ describe "without memoization" do
268
+ before do
269
+ @klass.stubs(:as_cache).returns({:store => :redis})
270
+ end
271
+ it "deletes key from cache store" do
272
+ @store.expects(:delete).with("mock.123")
273
+ CachedRecord::Cache.expire @instance
274
+ end
275
+ end
276
+ describe "with memoization" do
277
+ before do
278
+ @klass.stubs(:as_cache).returns({:store => :redis, :memoize => true})
279
+ end
280
+ it "deletes key from cache store and memoized cache" do
281
+ hash = mock
282
+ hash.expects(:delete).with("mock.123")
283
+ @store.expects(:delete).with("mock.123")
284
+ CachedRecord::Cache.expects(:cache).returns @store.class => hash
285
+ CachedRecord::Cache.expire @instance
286
+ end
287
+ end
288
+ end
289
+ describe ".memoized" do
290
+ describe "without memoization" do
291
+ it "returns nil" do
292
+ klass = mock
293
+ klass.expects(:as_cache).returns({})
294
+ instance = mock
295
+
296
+ CachedRecord::Cache.expects(:cache).never
297
+ assert_equal instance, CachedRecord::Cache.memoized(klass, 123, 123456789) { instance }
298
+ end
299
+ end
300
+ describe "with memoization" do
301
+ before do
302
+ @klass = mock
303
+ @klass.stubs(:as_cache).returns({:store => :redis, :memoize => true})
304
+ @klass.stubs(:cache_key).returns("mock.123")
305
+ end
306
+ describe "empty cache hash" do
307
+ it "returns nil" do
308
+ hash = mock
309
+ hash.expects(:[]).with("mock.123")
310
+ CachedRecord::Cache.expects(:cache).returns Redis => hash
311
+ assert_nil CachedRecord::Cache.memoized(@klass, 123, 123456789) {}
312
+ end
313
+ end
314
+ describe "matching cache hash entry" do
315
+ before do
316
+ @instance = mock
317
+ end
318
+ describe "non matching epoch time" do
319
+ it "returns nil" do
320
+ hash = mock
321
+ hash.expects(:[]).with("mock.123").returns :instance => mock, :epoch_time => 123456789
322
+ hash.expects(:[]=).with("mock.123", {:instance => @instance, :epoch_time => 987654321})
323
+
324
+ CachedRecord::Cache.expects(:cache).returns Redis => hash
325
+ assert_equal @instance, CachedRecord::Cache.memoized(@klass, 123, 987654321) { @instance }
326
+ end
327
+ end
328
+ describe "matching epoch time" do
329
+ it "returns memoized instance" do
330
+ hash = mock
331
+ hash.expects(:[]).with("mock.123").returns :instance => @instance, :epoch_time => 123456789
332
+
333
+ CachedRecord::Cache.expects(:cache).returns Redis => hash
334
+ assert_equal @instance, CachedRecord::Cache.memoized(@klass, 123, 123456789) { mock }
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end
340
+ describe ".split_cache_string" do
341
+ it "returns an array containing a JSON string and an optional epoch time integer" do
342
+ assert_equal ["", nil], CachedRecord::Cache.send(:split_cache_string, "")
343
+ assert_equal ['{"id":123,"foo":"b@r"}', nil], CachedRecord::Cache.send(:split_cache_string, '{"id":123,"foo":"b@r"}')
344
+ assert_equal ['{"id":123,"foo":"b@r"}', 123456789], CachedRecord::Cache.send(:split_cache_string, '{"id":123,"foo":"b@r"}@123456789')
345
+ end
346
+ end
347
+ end
348
+ end
349
+
350
+ end
351
+ end
@@ -0,0 +1,34 @@
1
+ require File.expand_path("../../test_helper", __FILE__)
2
+
3
+ module Unit
4
+ class TestCachedRecord < MiniTest::Test
5
+
6
+ describe CachedRecord do
7
+ it "has the current version" do
8
+ version = "0.1.0"
9
+ assert_equal version, CachedRecord::VERSION
10
+ assert File.read(path "CHANGELOG.rdoc").include?("Version #{version}")
11
+ assert File.read(path "VERSION").include?(version)
12
+ end
13
+
14
+ describe ".setup" do
15
+ it "sets up ActiveRecord, DataMapper and Cache" do
16
+ CachedRecord::Cache.expects(:redis).with(:host => "127.0.0.1")
17
+ CachedRecord::Cache.expects(:memcached).with(:host => "0.0.0.0")
18
+ CachedRecord::ORM::ActiveRecord.expects(:setup)
19
+ CachedRecord::ORM::DataMapper.expects(:setup)
20
+ CachedRecord.setup :redis => {:host => "127.0.0.1"}, :memcached => {:host => "0.0.0.0"}
21
+ end
22
+
23
+ it "accepts an array of cache stores" do
24
+ CachedRecord::Cache.expects(:redis)
25
+ CachedRecord::Cache.expects(:memcached)
26
+ CachedRecord::ORM::ActiveRecord.expects(:setup)
27
+ CachedRecord::ORM::DataMapper.expects(:setup)
28
+ CachedRecord.setup :redis, :memcached
29
+ end
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,193 @@
1
+ require File.expand_path("../../test_helper", __FILE__)
2
+
3
+ module Unit
4
+ class TestORM < MiniTest::Test
5
+
6
+ class A
7
+ include CachedRecord::ORM
8
+ as_cache :redis, "only" => [:title], :include => [:b], :memoize => [:sequence]
9
+ def id; end
10
+ end
11
+
12
+ class B
13
+ include CachedRecord::ORM
14
+ as_cache :redis, "only" => [:title], :include => [:b], "memoize" => {"calculate" => :@array}
15
+ end
16
+
17
+ class C
18
+ include CachedRecord::ORM
19
+ as_cache :redis, :only => [:title], "include" => [:b], :memoize => [:sequence, {:calculate => "@array"}]
20
+ end
21
+
22
+ class D
23
+ include CachedRecord::ORM
24
+ as_cache :redis, :only => [:title], :include_root => true
25
+ end
26
+
27
+ describe CachedRecord::ORM do
28
+ describe "when extended within a class" do
29
+
30
+ describe "classes" do
31
+ it "validates and parses 'as cache json options'" do
32
+ assert_raises ArgumentError do
33
+ A.as_cache :foonly => []
34
+ end
35
+ assert_raises ArgumentError do
36
+ A.as_cache :only => :foo
37
+ end
38
+ assert_raises ArgumentError do
39
+ A.as_cache :include => :foo
40
+ end
41
+ assert_raises ArgumentError do
42
+ A.as_cache :memoize => :foo
43
+ end
44
+ assert_raises ArgumentError do
45
+ A.as_cache :include_root => :foo
46
+ end
47
+ end
48
+ it "stores its cache options" do
49
+ assert_equal({
50
+ :store => :redis,
51
+ :as_json => {
52
+ :only => [:title],
53
+ :include => [:b],
54
+ :memoize => {:sequence => :@sequence}
55
+ }
56
+ }, A.as_cache)
57
+ assert_equal({
58
+ :store => :redis,
59
+ :as_json => {
60
+ :only => [:title],
61
+ :include => [:b],
62
+ :memoize => {:calculate => :@array}
63
+ }
64
+ }, B.as_cache)
65
+ assert_equal({
66
+ :store => :redis,
67
+ :as_json => {
68
+ :only => [:title],
69
+ :include => [:b],
70
+ :memoize => {:sequence => :@sequence, :calculate => :@array}
71
+ }
72
+ }, C.as_cache)
73
+ assert_equal({
74
+ :store => :redis,
75
+ :as_json => {
76
+ :only => [:title],
77
+ :include_root => true
78
+ }
79
+ }, D.as_cache)
80
+ end
81
+ it "stores whether it should memoize instances" do
82
+ options = {}
83
+ A.expects(:as_cache).with(:foo => :bar).returns(options)
84
+ A.as_memoized_cache :foo => :bar
85
+ assert_equal options, {:memoize => true}
86
+ end
87
+ it "memoizes its cache options" do
88
+ options = A.as_cache
89
+ assert_equal options.object_id, A.as_cache.object_id
90
+ end
91
+ it "returns cache keys" do
92
+ assert_equal "unit.test_orm.a.123", A.cache_key(123)
93
+ end
94
+ it "returns the cache root" do
95
+ assert_equal :a, A.cache_root
96
+ end
97
+ it "requires an implemented `uncached` method" do
98
+ assert_raises NotImplementedError do
99
+ A.uncached 1
100
+ end
101
+ end
102
+ it "requires an implemented `set_cached_association` method" do
103
+ assert_raises NotImplementedError do
104
+ A.send :set_cached_association, :a, :b, :c
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "instances" do
110
+ it "requires an implemented `cache_attributes` method" do
111
+ assert_raises NotImplementedError do
112
+ A.new.cache_attributes
113
+ end
114
+ end
115
+ it "requires an implemented `cache_foreign_keys` method" do
116
+ assert_raises NotImplementedError do
117
+ A.new.cache_foreign_keys
118
+ end
119
+ end
120
+ it "knows its as cache JSON options" do
121
+ assert_equal({
122
+ :only => [:title],
123
+ :include => [:b],
124
+ :memoize => {:sequence => :@sequence}
125
+ }, A.new.send(:cache_json_options))
126
+ assert_equal({
127
+ :only => [:title],
128
+ :include => [:b],
129
+ :memoize => {:calculate => :@array}
130
+ }, B.new.send(:cache_json_options))
131
+ assert_equal({
132
+ :only => [:title],
133
+ :include => [:b],
134
+ :memoize => {:sequence => :@sequence, :calculate => :@array}
135
+ }, C.new.send(:cache_json_options))
136
+ assert_equal({
137
+ :only => [:title],
138
+ :include_root => true
139
+ }, D.new.send(:cache_json_options))
140
+ end
141
+ it "returns a cache JSON string" do
142
+ hash = mock
143
+ hash.expects(:to_json)
144
+ a = A.new
145
+ a.expects(:as_cache_json).returns(hash)
146
+ a.to_cache_json
147
+ end
148
+ end
149
+
150
+ describe ".cached" do
151
+ describe "when not having a cache entry" do
152
+ it "returns an uncached instance and stores its cache JSON in the cache store" do
153
+ uncached_instance = A.new
154
+ uncached_instance.expects(:to_cache_json).returns({})
155
+
156
+ A.stubs(:as_cache).returns({:store => :redis, :as_json => {}})
157
+ A.expects(:uncached).with(123).returns uncached_instance
158
+ A.expects(:new).returns uncached_instance
159
+
160
+ assert_equal uncached_instance, A.cached(123)
161
+ end
162
+ end
163
+ describe "when having a cache entry" do
164
+ it "returns a cached instance" do
165
+ cached_instance = mock
166
+ CachedRecord::Cache.expects(:get).with(A, 123).returns cached_instance
167
+ assert_equal cached_instance, A.cached(123)
168
+ end
169
+ end
170
+ end
171
+
172
+ describe "#cache" do
173
+ it "delegates to class.cached" do
174
+ id, instance = 2, A.new
175
+ instance.expects(:id).returns(id)
176
+ A.expects(:cached).with(id)
177
+ assert instance.cache
178
+ end
179
+ end
180
+
181
+ describe "#expire" do
182
+ it "delegates to CachedRecord::Cache.expire" do
183
+ instance = A.new
184
+ CachedRecord::Cache.expects(:expire).with(instance)
185
+ assert instance.expire
186
+ end
187
+ end
188
+
189
+ end
190
+ end
191
+
192
+ end
193
+ end