cached_record 0.1.0

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.
@@ -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