generation_cacheable 1.0.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.
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