generation_cacheable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/ChangeLog +2 -0
  7. data/Gemfile +16 -0
  8. data/README.md +33 -0
  9. data/Rakefile +8 -0
  10. data/generation_cacheable.gemspec +23 -0
  11. data/lib/gen_cache.rb +32 -0
  12. data/lib/gen_cache/cache_io/fetching.rb +78 -0
  13. data/lib/gen_cache/cache_io/formatting.rb +75 -0
  14. data/lib/gen_cache/cache_io/parsing.rb +66 -0
  15. data/lib/gen_cache/cache_types/association_cache.rb +50 -0
  16. data/lib/gen_cache/cache_types/attribute_cache.rb +30 -0
  17. data/lib/gen_cache/cache_types/class_method_cache.rb +23 -0
  18. data/lib/gen_cache/cache_types/key_cache.rb +14 -0
  19. data/lib/gen_cache/cache_types/method_cache.rb +28 -0
  20. data/lib/gen_cache/caches.rb +15 -0
  21. data/lib/gen_cache/expiry.rb +37 -0
  22. data/lib/gen_cache/keys.rb +70 -0
  23. data/lib/gen_cache/version.rb +3 -0
  24. data/spec/gen_cache/cache_io/fetching_spec.rb +41 -0
  25. data/spec/gen_cache/cache_io/formatting_spec.rb +59 -0
  26. data/spec/gen_cache/cache_io/parsing_spec.rb +47 -0
  27. data/spec/gen_cache/cache_types/association_cache_spec.rb +313 -0
  28. data/spec/gen_cache/cache_types/attribute_cache_spec.rb +98 -0
  29. data/spec/gen_cache/cache_types/class_method_cache_spec.rb +72 -0
  30. data/spec/gen_cache/cache_types/key_cache_spec.rb +34 -0
  31. data/spec/gen_cache/cache_types/method_cache_spec.rb +108 -0
  32. data/spec/gen_cache/expiry_spec.rb +182 -0
  33. data/spec/gen_cache/keys_spec.rb +66 -0
  34. data/spec/gen_cache_spec.rb +76 -0
  35. data/spec/models/account.rb +7 -0
  36. data/spec/models/comment.rb +9 -0
  37. data/spec/models/descendant.rb +20 -0
  38. data/spec/models/group.rb +6 -0
  39. data/spec/models/image.rb +11 -0
  40. data/spec/models/location.rb +3 -0
  41. data/spec/models/post.rb +31 -0
  42. data/spec/models/tag.rb +6 -0
  43. data/spec/models/user.rb +54 -0
  44. data/spec/spec_helper.rb +96 -0
  45. metadata +149 -0
@@ -0,0 +1,23 @@
1
+ module GenCache
2
+ module ClassMethodCache
3
+ # Cached class method
4
+ # Should expire on any instance save
5
+ def with_class_method(*methods)
6
+ self.cached_class_methods ||= []
7
+ self.cached_class_methods += methods
8
+
9
+ methods.each do |meth|
10
+ define_singleton_method("cached_#{meth}") do |*args|
11
+ cache_key = GenCache.class_method_key(self, meth)
12
+ GenCache.fetch(cache_key, args: args) do
13
+ unless args.empty?
14
+ self.send(meth, *args)
15
+ else
16
+ self.send(meth)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,14 @@
1
+ module GenCache
2
+ module KeyCache
3
+ def with_key
4
+ self.cached_key = true
5
+
6
+ define_singleton_method("find_cached") do |id|
7
+ cache_key = GenCache.instance_key(self, id)
8
+ GenCache.fetch(cache_key) do
9
+ self.find(id)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ module GenCache
2
+ module MethodCache
3
+ def with_method(*methods)
4
+ self.cached_methods ||= []
5
+ self.cached_methods += methods
6
+
7
+ methods.each do |meth|
8
+ method_name = "cached_#{meth}"
9
+ define_method("cached_#{meth}") do |*args|
10
+ args ||= []
11
+ cache_key = GenCache.method_key(self, meth)
12
+ memoized_name = GenCache.escape_punctuation("@#{method_name}")
13
+ if instance_variable_get(memoized_name).nil?
14
+ result = GenCache.fetch(cache_key, args: args) do
15
+ unless args.empty?
16
+ self.send(meth, *args)
17
+ else
18
+ self.send(meth)
19
+ end
20
+ end
21
+ instance_variable_set(memoized_name, result)
22
+ end
23
+ instance_variable_get(memoized_name)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ require "gen_cache/cache_types/key_cache"
2
+ require "gen_cache/cache_types/attribute_cache"
3
+ require "gen_cache/cache_types/method_cache"
4
+ require "gen_cache/cache_types/class_method_cache"
5
+ require "gen_cache/cache_types/association_cache"
6
+
7
+ module GenCache
8
+ module Caches
9
+ include GenCache::KeyCache
10
+ include GenCache::AttributeCache
11
+ include GenCache::MethodCache
12
+ include GenCache::ClassMethodCache
13
+ include GenCache::AssocationCache
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ module GenCache
2
+
3
+ def expire_all
4
+ GenCache.expire(self)
5
+ end
6
+
7
+ # Manual expiry initiated by an object after commit
8
+ # only has to worry about key_cache, attribute_cache, and class_method_cache
9
+ def self.expire(object)
10
+ expire_instance_key(object)
11
+ expire_class_method_keys(object)
12
+ expire_attribute_keys(object)
13
+ end
14
+
15
+ def self.expire_instance_key(object)
16
+ key = instance_key(object.class, object.id)
17
+ Rails.cache.delete(key[:key])
18
+ end
19
+
20
+ def self.expire_class_method_keys(object)
21
+ object.class.cached_class_methods.map do |class_method|
22
+ key = class_method_key(object.class, class_method)
23
+ end.each do |method_key|
24
+ Rails.cache.delete(method_key[:key])
25
+ end
26
+ end
27
+
28
+ # cached attributes are stored like:
29
+ # { attribute => [value1, value2] }
30
+ def self.expire_attribute_keys(object)
31
+ object.class.cached_indices.map do |index, values|
32
+ values.map { |v| attribute_key(object.class, index, v) }
33
+ end.flatten.each do |attribute_key|
34
+ Rails.cache.delete(attribute_key[:key])
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
1
+ module GenCache
2
+
3
+ # Keys generated in hashes called Key blobs => { type: :key_type, key: 'key' }
4
+ # The type is used by the fetcher so it doesn't have to parse
5
+ # the key to figure out what kind it is and how to handle its contents.
6
+
7
+ ## CLASS KEYS
8
+
9
+ # HASH generated from SCHEMA to indicate MODEL GENERATIONS
10
+ # => "users/5821759535148822589"
11
+ def self.model_prefix(klass)
12
+ columns = klass.try(:columns)
13
+ return if columns.nil?
14
+ schema_string = columns.sort_by(&:name).map{|c| "#{c.name}:#{c.type}"}.join(',')
15
+ generation = CityHash.hash64(schema_string)
16
+ [klass.name.tableize, generation].join("/")
17
+ end
18
+
19
+ # => "users/5821759535148822589/64"
20
+ def self.instance_key(klass, id)
21
+ {type: :object,
22
+ key: [model_prefix(klass), id].join("/") }
23
+ end
24
+
25
+ # => "users/5821759535148822589/attribute/value"
26
+ # => "users/5821759535148822589/all/attribute/value"
27
+ def self.attribute_key(klass, attribute, args, options={})
28
+ att_args = [attribute, symbolize_args([args])].join("/")
29
+ unless options[:all]
30
+ { type: :object,
31
+ key: [model_prefix(klass), att_args].join("/") }
32
+ else
33
+ { type: :object,
34
+ key: [model_prefix(klass), "all", att_args].join("/") }
35
+ end
36
+ end
37
+
38
+ # => "users/5821759535148822589/method"
39
+ def self.class_method_key(klass, method)
40
+ { type: :method,
41
+ key: [model_prefix(klass), method].join("/") }
42
+ end
43
+
44
+ def self.all_class_method_keys(klass)
45
+ klass.cached_class_methods.map { |c_method| class_method_key(klass, c_method) }
46
+ end
47
+
48
+ ## INSTANCE KEYS
49
+
50
+ # HASH generated from ATTRIBUTES to indicate INSTANCE GENERATIONS
51
+ # => "users/5821759535148822589/64/12126514016877773284"
52
+ def self.instance_prefix(instance)
53
+ atts = instance.attributes
54
+ att_string = atts.sort.map { |k, v| [k,v].join(":") }.join(",")
55
+ generation = CityHash.hash64(att_string)
56
+ [model_prefix(instance.class), instance.id, generation].join("/")
57
+ end
58
+
59
+ # => "users/5821759535148822589/64/12126514016877773284/method"
60
+ def self.method_key(instance, method)
61
+ { type: :method,
62
+ key: [instance_prefix(instance), method].join("/") }
63
+ end
64
+
65
+ # => "users/5821759535148822589/64/12126514016877773284/association"
66
+ def self.association_key(instance, association)
67
+ { type: :association,
68
+ key: [instance_prefix(instance), association].join("/") }
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ module GenerationCacheable
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe GenCache do
4
+ let(:cache) { Rails.cache }
5
+ let(:user) { User.create(:login => 'flyerhzm') }
6
+ let(:user2) { User.create(:login => 'pathouse') }
7
+
8
+ describe "read" do
9
+
10
+ it "should successfully read one key" do
11
+ user.cached_bad_iv_name!
12
+ key = GenCache.method_key(user, :bad_iv_name!)
13
+ GenCache.read_from_cache(key).should == 42
14
+ end
15
+
16
+ it "should successfully read multiple keys" do
17
+ User.find_cached(user.id)
18
+ User.find_cached(user2.id)
19
+
20
+ key_blobs = [GenCache.instance_key(user.class, user.id),
21
+ GenCache.instance_key(user2.class, user2.id)]
22
+ GenCache.read_multi_from_cache(key_blobs).should == {key_blobs[0][:key] => user,
23
+ key_blobs[1][:key] => user2}
24
+ end
25
+
26
+ it "should successfully write one key" do
27
+ key = GenCache.instance_key(user.class, user.id)
28
+ GenCache.write_to_cache(key, user)
29
+ Rails.cache.read(key[:key]).should == {:class => User, 'attributes' => user.attributes}
30
+ end
31
+
32
+ it "should successfully write multiple keys" do
33
+ keys = []
34
+ keys << GenCache.instance_key(user.class, user.id)
35
+ keys << GenCache.instance_key(user2.class, user2.id)
36
+ GenCache.write_multi_to_cache({keys[0] => user, keys[1] => user2})
37
+ Rails.cache.read_multi(*keys.map {|k| k[:key]}).should == {keys[0][:key] => {:class => User, 'attributes' => user.attributes},
38
+ keys[1][:key] => {:class => User, 'attributes' => user2.attributes}}
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ describe GenCache do
4
+ let(:object) { User.create(:login => 'flyerhzm') }
5
+ let(:fixnum) { 11 }
6
+ let(:string) { "string cheese" }
7
+ let(:hash) { {a: 1, b: 2, c: 3} }
8
+ let(:array) { ['a','b','c']}
9
+ let(:bool) { true }
10
+
11
+ context "methods" do
12
+
13
+ it "should symbolize args correctly" do
14
+ argsym = GenCache.symbolize_args([fixnum, string, hash, array])
15
+ argsym.should == "11+string_cheese+a:1,b:2,c:3+a,b,c".to_sym
16
+ end
17
+
18
+ it "should escape method name punctuation correctly" do
19
+ GenCache.escape_punctuation("holy_crap?").should == "holy_crap_query"
20
+ GenCache.escape_punctuation("holy_crap!").should == "holy_crap_bang"
21
+ end
22
+
23
+ it "should format objects correctly" do
24
+ GenCache.format_with_key(object, :object).should == { :class => object.class, 'attributes' => object.attributes}
25
+ end
26
+
27
+ it "should format multiple object correctly" do
28
+ coder = { :class => object.class, 'attributes' => object.attributes}
29
+ GenCache.format_with_key([object, object], :object).should == [coder, coder]
30
+ end
31
+
32
+ it "should format methods without arguments correctly" do
33
+ GenCache.format_with_key(fixnum, :method).should == 11
34
+ end
35
+
36
+ it "should format method with arguments correctly" do
37
+ arg1 = GenCache.symbolize_args([fixnum,string,hash])
38
+ arg2 = GenCache.symbolize_args([string,hash,fixnum])
39
+ to_be_formatted = { arg1 => object,
40
+ arg2 => object}
41
+ formatted = GenCache.format_with_key(to_be_formatted, :method)
42
+ formatted[arg1].should == {:class => object.class, 'attributes' => object.attributes }
43
+ formatted[arg2].should == {:class => object.class, 'attributes' => object.attributes }
44
+ formatted[arg1].should_not == object
45
+ formatted[arg2].should_not == object
46
+ end
47
+
48
+ it "should format object correctly when returned from a method" do
49
+ arg1 = GenCache.symbolize_args([fixnum,string])
50
+ method_result = { arg1 => object }
51
+ GenCache.format_with_key(method_result, :method).should == { arg1 => {:class => object.class, 'attributes' => object.attributes} }
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+
58
+
59
+
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe GenCache do
4
+ let(:object) { User.create(:login => 'flyerhzm') }
5
+ let(:coder) { {:class => object.class, 'attributes' => object.attributes} }
6
+ let(:fixnum) { 11 }
7
+ let(:string) { "string cheese" }
8
+ let(:hash) { {a: 1, b: 2, c: 3} }
9
+ let(:array) { ['a','b','c'] }
10
+ let(:bool) { true }
11
+
12
+ context "methods" do
13
+
14
+ it "should correctly determine if a Hash is a coder" do
15
+ GenCache.hash_inspect(hash).should be_false
16
+ GenCache.hash_inspect(coder).should be_true
17
+ end
18
+
19
+ it "should detect coders and coders in arrays" do
20
+ GenCache.detect_coder(coder).should be_true
21
+ GenCache.detect_coder([coder]).should be_true
22
+ end
23
+
24
+ it "should correctly rebuild objects from coders" do
25
+ GenCache.parse_with_key(coder, :object).should == object
26
+ end
27
+
28
+ it "should rebuild multiple objects" do
29
+ GenCache.parse_with_key([coder, coder], :object).should == [object, object]
30
+ end
31
+
32
+ it "should parse only the values of method results" do
33
+ arg1 = GenCache.symbolize_args([string,hash])
34
+ arg2 = GenCache.symbolize_args([array,bool])
35
+ method_result = { arg1 => [coder],
36
+ arg2 => string }
37
+ parsed = GenCache.parse_with_key(method_result, :method)
38
+ parsed[arg1].should == [object]
39
+ parsed[arg2].should == string
40
+ end
41
+
42
+ it "should correctly parse methods without arguments" do
43
+ method_result = {"regular" => "hash"}
44
+ GenCache.parse_with_key(method_result, :method).should == method_result
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,313 @@
1
+ require 'spec_helper'
2
+
3
+ describe GenCache do
4
+ let(:cache) { Rails.cache }
5
+ let(:user) { User.create(:login => 'flyerhzm') }
6
+ let(:user2) { User.create(:login => 'ScotterC') }
7
+
8
+
9
+ before :all do
10
+ @post1 = user.posts.create(:title => 'post1')
11
+ @post2 = user.posts.create(:title => 'post2')
12
+ @post3 = Post.create
13
+ @image1 = @post1.images.create
14
+ @image2 = @post1.images.create
15
+ @comment1 = @post1.comments.create
16
+ @comment2 = @post1.comments.create
17
+ @tag1 = @post1.tags.create(title: "Rails")
18
+ @tag2 = @post1.tags.create(title: "Caching")
19
+ @group1 = Group.create(name: "Ruby On Rails")
20
+ @account = user.create_account(group: @group1)
21
+ @location = @post1.create_location(city: "New York")
22
+ end
23
+
24
+ before :each do
25
+ cache.clear
26
+ user.reload
27
+ end
28
+
29
+ context "with_association" do
30
+ before :each do
31
+ @post1.instance_variable_set("@cached_user", nil)
32
+ @comment1.instance_variable_set("@cached_commentable", nil)
33
+ end
34
+
35
+ context "belongs_to" do
36
+ it "should not cache association" do
37
+ key = GenCache.association_key(@post1, :user)
38
+ Rails.cache.read(key[:key]).should be_nil
39
+ end
40
+
41
+ it "should cache Post#user" do
42
+ @post1.cached_user.should == user
43
+ key = GenCache.association_key(@post1, :user)
44
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(User, user.id)]
45
+ end
46
+
47
+ it "should cache Post#user multiple times" do
48
+ @post1.cached_user
49
+ @post1.cached_user.should == user
50
+ end
51
+
52
+ it "should cache Comment#commentable with polymorphic" do
53
+ key = GenCache.association_key(@comment1, :commentable)
54
+ Rails.cache.read(key[:key]).should be_nil
55
+ @comment1.cached_commentable.should == @post1
56
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Post, @post1.id)]
57
+ end
58
+
59
+ it "should return nil if there are none" do
60
+ @post3.cached_user.should be_nil
61
+ end
62
+ end
63
+
64
+ context "has_many" do
65
+ it "should not cache associations" do
66
+ key = GenCache.association_key(user, :posts)
67
+ Rails.cache.read(key[:key]).should be_nil
68
+ end
69
+
70
+ it "should cache User#posts" do
71
+ user.cached_posts.should == [@post1, @post2]
72
+ key = GenCache.association_key(user, :posts)
73
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Post, @post1.id),
74
+ GenCache.instance_key(Post, @post2.id)]
75
+ end
76
+
77
+ it "should cache User#posts multiple times" do
78
+ user.cached_posts
79
+ user.cached_posts.should == [@post1, @post2]
80
+ end
81
+
82
+ it "should return empty if there are none" do
83
+ user2.cached_posts.should == []
84
+ end
85
+ end
86
+
87
+ context "has_many with polymorphic" do
88
+ it "should not cache associations" do
89
+ key = GenCache.association_key(@post1, :comments)
90
+ Rails.cache.read(key[:key]).should be_nil
91
+ end
92
+
93
+ it "should cache Post#comments" do
94
+ @post1.cached_comments.should == [@comment1, @comment2]
95
+ key = GenCache.association_key(@post1, :comments)
96
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Comment, @comment1.id),
97
+ GenCache.instance_key(Comment, @comment2.id)]
98
+ end
99
+
100
+ it "should cache Post#comments multiple times" do
101
+ @post1.cached_comments
102
+ @post1.cached_comments.should == [@comment1, @comment2]
103
+ end
104
+
105
+ it "should return empty if there are none" do
106
+ @post3.cached_comments.should == []
107
+ end
108
+ end
109
+
110
+ context "has_one" do
111
+ it "should not cache associations" do
112
+ key = GenCache.association_key(user, :account)
113
+ Rails.cache.read(key[:key]).should be_nil
114
+ end
115
+
116
+ it "should cache User#posts" do
117
+ user.cached_account.should == @account
118
+ key = GenCache.association_key(user, :account)
119
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Account, @account.id)]
120
+ end
121
+
122
+ it "should cache User#posts multiple times" do
123
+ user.cached_account
124
+ user.cached_account.should == @account
125
+ end
126
+
127
+ it "should return nil if there are none" do
128
+ user2.cached_account.should be_nil
129
+ end
130
+ end
131
+
132
+ context "has_many through" do
133
+ it "should not cache associations" do
134
+ key = GenCache.association_key(user, :images)
135
+ Rails.cache.read(key[:key]).should be_nil
136
+ end
137
+
138
+ it "should cache User#images" do
139
+ user.cached_images.should == [@image1, @image2]
140
+ key = GenCache.association_key(user, :images)
141
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Image, @image1.id),
142
+ GenCache.instance_key(Image, @image2.id)]
143
+ end
144
+
145
+ it "should cache User#images multiple times" do
146
+ user.cached_images
147
+ user.cached_images.should == [@image1, @image2]
148
+ end
149
+
150
+ context "expiry" do
151
+ before :each do
152
+ user.instance_variable_set("@cached_images", nil)
153
+ end
154
+
155
+ it "should have the correct collection" do
156
+ @image3 = @post1.images.create
157
+ key = GenCache.association_key(user, :images)
158
+ Rails.cache.read(key[:key]).should be_nil
159
+ user.cached_images.should == [@image1, @image2, @image3]
160
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Image, @image1.id),
161
+ GenCache.instance_key(Image, @image2.id),
162
+ GenCache.instance_key(Image, @image3.id)]
163
+ end
164
+ end
165
+
166
+ it "should return empty if there are none" do
167
+ user2.cached_images.should == []
168
+ end
169
+ end
170
+
171
+ context "has_one through belongs_to" do
172
+ it "should not cache associations" do
173
+ key = GenCache.association_key(user, :group)
174
+ Rails.cache.read(key[:key]).should be_nil
175
+ end
176
+
177
+ it "should cache User#group" do
178
+ user.cached_group.should == @group1
179
+ key = GenCache.association_key(user, :group)
180
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Group, @group1.id)]
181
+ end
182
+
183
+ it "should cache User#group multiple times" do
184
+ user.cached_group
185
+ user.cached_group.should == @group1
186
+ end
187
+
188
+ it "should return nil if there are none" do
189
+ user2.cached_group.should be_nil
190
+ end
191
+
192
+ end
193
+
194
+ context "has_and_belongs_to_many" do
195
+
196
+ it "should not cache associations off the bat" do
197
+ key = GenCache.association_key(@post1, :tags)
198
+ Rails.cache.read(key[:key]).should be_nil
199
+ end
200
+
201
+ it "should cache Post#tags" do
202
+ @post1.cached_tags.should == [@tag1, @tag2]
203
+ key = GenCache.association_key(@post1, :tags)
204
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Tag, @tag1.id),
205
+ GenCache.instance_key(Tag, @tag2.id)]
206
+ end
207
+
208
+ it "should handle multiple requests" do
209
+ @post1.cached_tags
210
+ @post1.cached_tags.should == [@tag1, @tag2]
211
+ end
212
+
213
+ it "should return empty if there are none" do
214
+ @post3.cached_tags.should == []
215
+ end
216
+
217
+ context "expiry" do
218
+ before :each do
219
+ @post1.instance_variable_set("@cached_tags", nil)
220
+ end
221
+
222
+ it "should have the correct collection" do
223
+ @tag3 = @post1.tags.create!(title: "Invalidation is hard")
224
+ key = GenCache.association_key(@post1, :tags)
225
+ Rails.cache.read(key[:key]).should be_nil
226
+ @post1.cached_tags.should == [@tag1, @tag2, @tag3]
227
+ Rails.cache.read(key[:key]).should == [GenCache.instance_key(Tag, @tag1.id),
228
+ GenCache.instance_key(Tag, @tag2.id),
229
+ GenCache.instance_key(Tag, @tag3.id)]
230
+ end
231
+ end
232
+ end
233
+ end
234
+
235
+ describe "memoization" do
236
+ describe "belongs to" do
237
+ before :each do
238
+ @post1.instance_variable_set("@cached_user", nil)
239
+ @post1.update_attribute(:title, rand(10000).to_s)
240
+ end
241
+
242
+ it "memoizes cache calls" do
243
+ @post1.instance_variable_get("@cached_user").should be_nil
244
+ @post1.cached_user.should == @post1.user
245
+ @post1.instance_variable_get("@cached_user").should == @post1.user
246
+ end
247
+
248
+ it "hits the cache only once" do
249
+ Rails.cache.expects(:read).once
250
+ @post1.cached_user.should == @post1.user
251
+ @post1.cached_user.should == @post1.user
252
+ end
253
+ end
254
+
255
+ describe "has through" do
256
+ before :each do
257
+ user.instance_variable_set("@cached_images", nil)
258
+ user.update_attribute(:login, rand(10000).to_s)
259
+ end
260
+
261
+ it "memoizes cache calls" do
262
+ user.instance_variable_get("@cached_images").should be_nil
263
+ user.cached_images.should == user.images
264
+ user.instance_variable_get("@cached_images").should == user.images
265
+ end
266
+
267
+ it "hits the cache only once" do
268
+ Rails.cache.expects(:read)
269
+ user.cached_images.should == user.images
270
+ user.cached_images.should == user.images
271
+ end
272
+ end
273
+
274
+ describe "has and belongs to many" do
275
+ before :each do
276
+ @post1.instance_variable_set("@cached_tags", nil)
277
+ @post1.update_attribute(:title, rand(10000).to_s)
278
+ end
279
+
280
+ it "memoizes cache calls" do
281
+ @post1.instance_variable_get("@cached_tags").should be_nil
282
+ @post1.cached_tags.should == @post1.tags
283
+ @post1.instance_variable_get("@cached_tags").should == @post1.tags
284
+ end
285
+
286
+ it "hits the cache only once" do
287
+ Rails.cache.expects(:read)
288
+ @post1.cached_tags.should == @post1.tags
289
+ @post1.cached_tags.should == @post1.tags
290
+ end
291
+ end
292
+
293
+ describe "one to many" do
294
+ before :each do
295
+ user.instance_variable_set("@cached_posts", nil)
296
+ user.update_attribute(:login, rand(10000).to_s)
297
+ end
298
+
299
+ it "memoizes cache calls" do
300
+ user.instance_variable_get("@cached_posts").should be_nil
301
+ user.cached_posts.should == user.posts
302
+ user.instance_variable_get("@cached_posts").should == user.posts
303
+ end
304
+
305
+ it "hits the cache only once" do
306
+ Rails.cache.expects(:read)
307
+ user.cached_posts.should == user.posts
308
+ user.cached_posts.should == user.posts
309
+ end
310
+ end
311
+ end
312
+
313
+ end