simple_cacheable 1.3.2 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/cacheable.rb CHANGED
@@ -1,273 +1,24 @@
1
1
  require 'uri'
2
+ require "cacheable/caches"
3
+ require "cacheable/keys"
4
+ require "cacheable/expiry"
2
5
 
3
6
  module Cacheable
4
7
  def self.included(base)
5
- base.class_eval do
6
- class << self
7
- def model_cache(&block)
8
- class_attribute :cached_key,
9
- :cached_indices,
10
- :cached_methods,
11
- :cached_class_methods,
12
- :cached_associations
13
- instance_exec &block
14
- end
15
-
16
- def with_key
17
- self.cached_key = true
18
-
19
- class_eval <<-EOF
20
- after_commit :expire_key_cache, :on => :update
21
-
22
- def self.find_cached(id)
23
- Rails.cache.fetch "#{name.tableize}/" + id.to_i.to_s do
24
- self.find(id)
25
- end
26
- end
27
- EOF
28
- end
29
-
30
- def with_attribute(*attributes)
31
- self.cached_indices = attributes.inject({}) { |indices, attribute| indices[attribute] = {} }
32
-
33
- class_eval <<-EOF
34
- after_commit :expire_attribute_cache, :on => :update
35
- after_commit :expire_all_attribute_cache, :on => :update
36
- EOF
37
-
38
- attributes.each do |attribute|
39
- class_eval <<-EOF
40
- def self.find_cached_by_#{attribute}(value)
41
- self.cached_indices["#{attribute}"] ||= []
42
- self.cached_indices["#{attribute}"] << value
43
- Rails.cache.fetch attribute_cache_key("#{attribute}", value) do
44
- self.find_by_#{attribute}(value)
45
- end
46
- end
47
-
48
- def self.find_cached_all_by_#{attribute}(value)
49
- self.cached_indices["#{attribute}"] ||= []
50
- self.cached_indices["#{attribute}"] << value
51
- Rails.cache.fetch all_attribute_cache_key("#{attribute}", value) do
52
- self.find_all_by_#{attribute}(value)
53
- end
54
- end
55
- EOF
56
- end
57
- end
58
-
59
- def with_method(*methods)
60
- self.cached_methods = methods
61
-
62
- class_eval <<-EOF
63
- after_commit :expire_method_cache, :on => :update
64
- EOF
65
-
66
- methods.each do |meth|
67
- class_eval <<-EOF
68
- def cached_#{meth}
69
- Rails.cache.fetch method_cache_key("#{meth}") do
70
- #{meth}
71
- end
72
- end
73
- EOF
74
- end
75
- end
76
-
77
- # Cached class method
78
- # Should expire on any instance save
79
- def with_class_method(*methods)
80
- self.cached_class_methods = methods
81
-
82
- class_eval <<-EOF
83
- after_commit :expire_class_method_cache, on: :update
84
- EOF
85
-
86
- methods.each do |meth|
87
- class_eval <<-EOF
88
- def self.cached_#{meth}
89
- Rails.cache.fetch class_method_cache_key("#{meth}") do
90
- #{meth}
91
- end
92
- end
93
- EOF
94
- end
95
-
96
- end
97
-
98
- def with_association(*association_names)
99
- self.cached_associations = association_names
100
-
101
- association_names.each do |association_name|
102
- association = reflect_on_association(association_name)
103
-
104
- if :belongs_to == association.macro
105
- polymorphic = association.options[:polymorphic]
106
- polymorphic ||= false
107
- class_eval <<-EOF
108
- def cached_#{association_name}
109
- Rails.cache.fetch belong_association_cache_key("#{association_name}", #{polymorphic}) do
110
- #{association_name}
111
- end
112
- end
113
- EOF
114
- else
115
- if through_reflection_name = association.options[:through]
116
- through_association = self.reflect_on_association(through_reflection_name)
117
-
118
- # FIXME it should be the only reflection but I'm not 100% positive
119
- reverse_through_association = through_association.klass.reflect_on_all_associations(:belongs_to).first
120
-
121
- # In a through association it doesn't have to be a belongs_to
122
- reverse_association = association.klass.reflect_on_all_associations(:belongs_to).find { |reverse_association|
123
- reverse_association.options[:polymorphic] ? reverse_association.name == association.source_reflection.options[:as] : reverse_association.klass == self
124
- }
125
-
126
- if reverse_association
127
- association.klass.class_eval <<-EOF
128
- after_commit :expire_#{association_name}_cache
129
-
130
- def expire_#{association_name}_cache
131
- if respond_to? :cached_#{reverse_association.name}
132
- # cached_viewable.expire_association_cache
133
- cached_#{reverse_association.name}.expire_association_cache(:#{association_name})
134
- else
135
- #{reverse_association.name}.#{reverse_through_association.name}.expire_association_cache(:#{association_name})
136
- end
137
- end
138
- EOF
139
- end
140
- elsif :has_and_belongs_to_many == association.macro
141
- # No such thing as a polymorphic has_and_belongs_to_many
142
- reverse_association = association.klass.reflect_on_all_associations(:has_and_belongs_to_many).find { |reverse_association|
143
- reverse_association.klass == self
144
- }
145
-
146
- association.klass.class_eval <<-EOF
147
- after_commit :expire_#{association_name}_cache
148
-
149
- def expire_#{association_name}_cache
150
- if respond_to? :cached_#{reverse_association.name}
151
- # cached_viewable.expire_association_cache
152
- cached_#{reverse_association.name}.expire_association_cache(:#{association_name})
153
- else
154
- #{reverse_association.name}.each do |assoc|
155
- assoc.expire_association_cache(:#{association_name})
156
- end
157
- end
158
- end
159
- EOF
160
- else
161
- reverse_association = association.klass.reflect_on_all_associations(:belongs_to).find { |reverse_association|
162
- reverse_association.options[:polymorphic] ? reverse_association.name == association.options[:as] : reverse_association.klass == self
163
- }
164
-
165
- association.klass.class_eval <<-EOF
166
- after_commit :expire_#{association_name}_cache
167
-
168
- def expire_#{association_name}_cache
169
- if respond_to? :cached_#{reverse_association.name}
170
- cached_#{reverse_association.name}.expire_association_cache(:#{association_name})
171
- else
172
- #{reverse_association.name}.expire_association_cache(:#{association_name})
173
- end
174
- end
175
- EOF
176
- end
177
- class_eval <<-EOF
178
- def cached_#{association_name}
179
- Rails.cache.fetch have_association_cache_key("#{association_name}") do
180
- #{association_name}.respond_to?(:all) ? #{association_name}.all : #{association_name}
181
- end
182
- end
183
- EOF
184
- end
185
- end
186
- end
187
-
188
- def attribute_cache_key(attribute, value)
189
- "#{name.tableize}/attribute/#{attribute}/#{URI.escape(value.to_s)}"
190
- end
191
-
192
- def all_attribute_cache_key(attribute, value)
193
- "#{name.tableize}/attribute/#{attribute}/all/#{URI.escape(value.to_s)}"
194
- end
195
-
196
- def class_method_cache_key(meth)
197
- "#{name.tableize}/class_method/#{meth}"
198
- end
199
-
200
- end
8
+ base.extend(Cacheable::Caches)
9
+ base.send :include, Cacheable::Keys
10
+ base.send :include, Cacheable::Expiry
201
11
 
202
- end
203
-
204
- end
205
-
206
- def expire_model_cache
207
- expire_key_cache if self.class.cached_key
208
- expire_attribute_cache if self.class.cached_indices.present?
209
- expire_all_attribute_cache if self.class.cached_indices.present?
210
- expire_method_cache if self.class.cached_methods.present?
211
- expire_class_method_cache if self.class.cached_class_methods.present?
212
-
213
- if self.class.cached_associations.present?
214
- self.class.cached_associations.each do |assoc|
215
- expire_association_cache(assoc)
12
+ base.class_eval do
13
+ def self.model_cache(&block)
14
+ class_attribute :cached_key,
15
+ :cached_indices,
16
+ :cached_methods,
17
+ :cached_class_methods,
18
+ :cached_associations
19
+ instance_exec &block
216
20
  end
217
21
  end
218
22
  end
219
23
 
220
- def expire_key_cache
221
- Rails.cache.delete model_cache_key
222
- end
223
-
224
- def expire_attribute_cache
225
- self.class.cached_indices.each do |attribute, values|
226
- value = self.send(attribute)
227
- Rails.cache.delete self.class.attribute_cache_key(attribute, value)
228
- end
229
- end
230
-
231
- def expire_all_attribute_cache
232
- self.class.cached_indices.each do |attribute, values|
233
- value = self.send(attribute)
234
- Rails.cache.delete self.class.all_attribute_cache_key(attribute, value)
235
- end
236
- end
237
-
238
- def expire_method_cache
239
- self.class.cached_methods.each do |meth|
240
- Rails.cache.delete method_cache_key(meth)
241
- end
242
- end
243
-
244
- def expire_class_method_cache
245
- self.class.cached_class_methods.each do |meth|
246
- Rails.cache.delete self.class.class_method_cache_key(meth)
247
- end
248
- end
249
-
250
- def expire_association_cache(name)
251
- Rails.cache.delete have_association_cache_key(name)
252
- end
253
-
254
- def model_cache_key
255
- "#{self.class.name.tableize}/#{self.id.to_i}"
256
- end
257
-
258
- def method_cache_key(meth)
259
- "#{model_cache_key}/method/#{meth}"
260
- end
261
-
262
- def belong_association_cache_key(name, polymorphic=nil)
263
- if polymorphic
264
- "#{self.send("#{name}_type").tableize}/#{self.send("#{name}_id")}"
265
- else
266
- "#{name.tableize}/#{self.send(name + "_id")}"
267
- end
268
- end
269
-
270
- def have_association_cache_key(name)
271
- "#{model_cache_key}/association/#{name}"
272
- end
273
24
  end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cacheable do
4
+ let(:cache) { Rails.cache }
5
+ let(:user) { User.create(:login => 'flyerhzm') }
6
+
7
+ before :all do
8
+ @post1 = user.posts.create(:title => 'post1')
9
+ user2 = User.create(:login => 'PelegR')
10
+ user2.posts.create(:title => 'post3')
11
+ end
12
+
13
+ before :each do
14
+ cache.clear
15
+ user.reload
16
+ end
17
+
18
+ context "expire_model_cache" do
19
+ it "should delete with_key cache" do
20
+ User.find_cached(user.id)
21
+ Rails.cache.read("users/#{user.id}").should_not be_nil
22
+ user.expire_model_cache
23
+ Rails.cache.read("users/#{user.id}").should be_nil
24
+ end
25
+
26
+ it "should delete with_attribute cache" do
27
+ user = User.find_cached_by_login("flyerhzm")
28
+ Rails.cache.read("users/attribute/login/flyerhzm").should == user
29
+ user.expire_model_cache
30
+ Rails.cache.read("users/attribute/login/flyerhzm").should be_nil
31
+ end
32
+
33
+ it "should delete with_method cache" do
34
+ user.cached_last_post
35
+ Rails.cache.read("users/#{user.id}/method/last_post").should_not be_nil
36
+ user.expire_model_cache
37
+ Rails.cache.read("users/#{user.id}/method/last_post").should be_nil
38
+ end
39
+
40
+ it "should delete with_class_method cache (default_post)" do
41
+ Post.cached_default_post
42
+ Rails.cache.read("posts/class_method/default_post").should_not be_nil
43
+ @post1.expire_model_cache
44
+ Rails.cache.read("posts/class_method/default_post").should be_nil
45
+ end
46
+
47
+ it "should delete with_class_method cache (retrieve_with_user_id)" do
48
+ Post.cached_retrieve_with_user_id(1)
49
+ Rails.cache.read("posts/class_method/retrieve_with_user_id/1").should_not be_nil
50
+ @post1.expire_model_cache
51
+ Rails.cache.read("posts/class_method/retrieve_with_user_id/1").should be_nil
52
+ end
53
+
54
+ it "should delete with_class_method cache (retrieve_with_user_id) with different arguments" do
55
+ Post.cached_retrieve_with_user_id(1)
56
+ Post.cached_retrieve_with_user_id(2)
57
+ Rails.cache.read("posts/class_method/retrieve_with_user_id/1").should_not be_nil
58
+ Rails.cache.read("posts/class_method/retrieve_with_user_id/2").should_not be_nil
59
+ @post1.expire_model_cache
60
+ Rails.cache.read("posts/class_method/retrieve_with_user_id/1").should be_nil
61
+ Rails.cache.read("posts/class_method/retrieve_with_user_id/2").should be_nil
62
+ end
63
+
64
+ it "should delete with_class_method cache (retrieve_with_both)" do
65
+ Post.cached_retrieve_with_both(1, 1)
66
+ Rails.cache.read("posts/class_method/retrieve_with_both/1+1").should_not be_nil
67
+ @post1.expire_model_cache
68
+ Rails.cache.read("posts/class_method/retrieve_with_both/1+1").should be_nil
69
+ end
70
+
71
+ # TODO: should we cache empty arrays?
72
+ it "should delete associations cache" do
73
+ user.cached_images
74
+ Rails.cache.read("users/#{user.id}/association/images").should_not be_nil
75
+ user.expire_model_cache
76
+ Rails.cache.read("users/#{user.id}/association/images").should be_nil
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,166 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cacheable do
4
+ let(:cache) { Rails.cache }
5
+ let(:user) { User.create(:login => 'flyerhzm') }
6
+
7
+ before :all do
8
+ @post1 = user.posts.create(:title => 'post1')
9
+ @post2 = user.posts.create(:title => 'post2')
10
+ @image1 = @post1.images.create
11
+ @image2 = @post1.images.create
12
+ @comment1 = @post1.comments.create
13
+ @comment2 = @post1.comments.create
14
+ @tag1 = @post1.tags.create(title: "Rails")
15
+ @tag2 = @post1.tags.create(title: "Caching")
16
+ @group1 = Group.create(name: "Ruby On Rails")
17
+ @account = user.create_account(group: @group1)
18
+ end
19
+
20
+ before :each do
21
+ cache.clear
22
+ user.reload
23
+ end
24
+
25
+ context "with_association" do
26
+ context "belongs_to" do
27
+ it "should not cache association" do
28
+ Rails.cache.read("users/#{user.id}").should be_nil
29
+ end
30
+
31
+ it "should cache Post#user" do
32
+ @post1.cached_user.should == user
33
+ Rails.cache.read("users/#{user.id}").should == user
34
+ end
35
+
36
+ it "should cache Post#user multiple times" do
37
+ @post1.cached_user
38
+ @post1.cached_user.should == user
39
+ end
40
+
41
+ it "should cache Comment#commentable with polymorphic" do
42
+ Rails.cache.read("posts/#{@post1.id}").should be_nil
43
+ @comment1.cached_commentable.should == @post1
44
+ Rails.cache.read("posts/#{@post1.id}").should == @post1
45
+ end
46
+ end
47
+
48
+ context "has_many" do
49
+ it "should not cache associations" do
50
+ Rails.cache.read("users/#{user.id}/association/posts").should be_nil
51
+ end
52
+
53
+ it "should cache User#posts" do
54
+ user.cached_posts.should == [@post1, @post2]
55
+ Rails.cache.read("users/#{user.id}/association/posts").should == [@post1, @post2]
56
+ end
57
+
58
+ it "should cache User#posts multiple times" do
59
+ user.cached_posts
60
+ user.cached_posts.should == [@post1, @post2]
61
+ end
62
+ end
63
+
64
+ context "has_many with polymorphic" do
65
+ it "should not cache associations" do
66
+ Rails.cache.read("posts/#{@post1.id}/association/comments").should be_nil
67
+ end
68
+
69
+ it "should cache Post#comments" do
70
+ @post1.cached_comments.should == [@comment1, @comment2]
71
+ Rails.cache.read("posts/#{@post1.id}/association/comments").should == [@comment1, @comment2]
72
+ end
73
+
74
+ it "should cache Post#comments multiple times" do
75
+ @post1.cached_comments
76
+ @post1.cached_comments.should == [@comment1, @comment2]
77
+ end
78
+ end
79
+
80
+ context "has_one" do
81
+ it "should not cache associations" do
82
+ Rails.cache.read("users/#{user.id}/association/account").should be_nil
83
+ end
84
+
85
+ it "should cache User#posts" do
86
+ user.cached_account.should == @account
87
+ Rails.cache.read("users/#{user.id}/association/account").should == @account
88
+ end
89
+
90
+ it "should cache User#posts multiple times" do
91
+ user.cached_account
92
+ user.cached_account.should == @account
93
+ end
94
+ end
95
+
96
+ context "has_many through" do
97
+ it "should not cache associations" do
98
+ Rails.cache.read("users/#{user.id}/association/images").should be_nil
99
+ end
100
+
101
+ it "should cache User#images" do
102
+ user.cached_images.should == [@image1, @image2]
103
+ Rails.cache.read("users/#{user.id}/association/images").should == [@image1, @image2]
104
+ end
105
+
106
+ it "should cache User#images multiple times" do
107
+ user.cached_images
108
+ user.cached_images.should == [@image1, @image2]
109
+ end
110
+
111
+ context "expiry" do
112
+ it "should have the correct collection" do
113
+ @image3 = @post1.images.create
114
+ Rails.cache.read("users/#{user.id}/association/images").should be_nil
115
+ user.cached_images.should == [@image1, @image2, @image3]
116
+ Rails.cache.read("users/#{user.id}/association/images").should == [@image1, @image2, @image3]
117
+ end
118
+ end
119
+ end
120
+
121
+ context "has_one through belongs_to" do
122
+ it "should not cache associations" do
123
+ Rails.cache.read("users/#{user.id}/association/group").should be_nil
124
+ end
125
+
126
+ it "should cache User#group" do
127
+ user.cached_group.should == @group1
128
+ Rails.cache.read("users/#{user.id}/association/group").should == @group1
129
+ end
130
+
131
+ it "should cache User#group multiple times" do
132
+ user.cached_group
133
+ user.cached_group.should == @group1
134
+ end
135
+
136
+ end
137
+
138
+ context "has_and_belongs_to_many" do
139
+
140
+ it "should not cache associations off the bat" do
141
+ Rails.cache.read("posts/#{@post1.id}/association/tags").should be_nil
142
+ end
143
+
144
+ it "should cache Post#tags" do
145
+ @post1.cached_tags.should == [@tag1, @tag2]
146
+ Rails.cache.read("posts/#{@post1.id}/association/tags").should == [@tag1, @tag2]
147
+ end
148
+
149
+ it "should handle multiple requests" do
150
+ @post1.cached_tags
151
+ @post1.cached_tags.should == [@tag1, @tag2]
152
+ end
153
+
154
+ context "expiry" do
155
+ it "should have the correct collection" do
156
+ @tag3 = @post1.tags.create!(title: "Invalidation is hard")
157
+ Rails.cache.read("posts/#{@post1.id}/association/tags").should be_nil
158
+ @post1.cached_tags.should == [@tag1, @tag2, @tag3]
159
+ Rails.cache.read("posts/#{@post1.id}/association/tags").should == [@tag1, @tag2, @tag3]
160
+ end
161
+ end
162
+ end
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cacheable do
4
+ let(:cache) { Rails.cache }
5
+ let(:user) { User.create(:login => 'flyerhzm') }
6
+
7
+ before :all do
8
+ @post1 = user.posts.create(:title => 'post1')
9
+ @post2 = user.posts.create(:title => 'post2')
10
+ end
11
+
12
+ before :each do
13
+ cache.clear
14
+ user.reload
15
+ end
16
+
17
+ context "with_attribute" do
18
+ it "should not cache User.find_by_login" do
19
+ Rails.cache.read("users/attribute/login/flyerhzm").should be_nil
20
+ end
21
+
22
+ it "should cache by User.find_by_login" do
23
+ User.find_cached_by_login("flyerhzm").should == user
24
+ Rails.cache.read("users/attribute/login/flyerhzm").should == user
25
+ end
26
+
27
+ it "should get cached by User.find_by_login multiple times" do
28
+ User.find_cached_by_login("flyerhzm")
29
+ User.find_cached_by_login("flyerhzm").should == user
30
+ end
31
+
32
+ it "should escape whitespace" do
33
+ new_user = User.create(:login => "user space")
34
+ User.find_cached_by_login("user space").should == new_user
35
+ end
36
+
37
+ it "should handle fixed numbers" do
38
+ Post.find_cached_by_user_id(user.id).should == @post1
39
+ Rails.cache.read("posts/attribute/user_id/#{user.id}").should == @post1
40
+ end
41
+
42
+ context "find_all" do
43
+ it "should not cache Post.find_all_by_user_id" do
44
+ Rails.cache.read("posts/attribute/user_id/all/#{user.id}").should be_nil
45
+ end
46
+
47
+ it "should cache by Post.find_cached_all_by_user_id" do
48
+ Post.find_cached_all_by_user_id(user.id).should == [@post1, @post2]
49
+ Rails.cache.read("posts/attribute/user_id/all/#{user.id}").should == [@post1, @post2]
50
+ end
51
+
52
+ it "should get cached by Post.find_cached_all_by_user_id multiple times" do
53
+ Post.find_cached_all_by_user_id(user.id)
54
+ Post.find_cached_all_by_user_id(user.id).should == [@post1, @post2]
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cacheable do
4
+ let(:cache) { Rails.cache }
5
+ let(:user) { User.create(:login => 'flyerhzm') }
6
+
7
+ before :all do
8
+ @post1 = user.posts.create(:title => 'post1')
9
+ @post2 = user.posts.create(:title => 'post2')
10
+ end
11
+
12
+ before :each do
13
+ cache.clear
14
+ user.reload
15
+ end
16
+
17
+ context "with_class_method" do
18
+ it "should not cache Post.default_post" do
19
+ Rails.cache.read("posts/class_method/default_post").should be_nil
20
+ end
21
+
22
+ it "should cache Post.default_post" do
23
+ Post.cached_default_post.should == @post1
24
+ Rails.cache.read("posts/class_method/default_post").should == @post1
25
+ end
26
+
27
+ it "should cache Post.default_post multiple times" do
28
+ Post.cached_default_post
29
+ Post.cached_default_post.should == @post1
30
+ end
31
+
32
+ it "should cache Post.retrieve_with_user_id" do
33
+ Post.cached_retrieve_with_user_id(1).should == @post1
34
+ Rails.cache.read("posts/class_method/retrieve_with_user_id/1").should == @post1
35
+ end
36
+
37
+ it "should cache Post.retrieve_with_both with multiple arguments" do
38
+ Post.cached_retrieve_with_both(1, 1).should be_true
39
+ Rails.cache.read("posts/class_method/retrieve_with_both/1+1").should be_true
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cacheable do
4
+ let(:cache) { Rails.cache }
5
+ let(:user) { User.create(:login => 'flyerhzm') }
6
+
7
+ before :each do
8
+ cache.clear
9
+ user.reload
10
+ end
11
+
12
+ context "with_key" do
13
+ it "should not cache key" do
14
+ Rails.cache.read("users/#{user.id}").should be_nil
15
+ end
16
+
17
+ it "should cache by User#id" do
18
+ User.find_cached(user.id).should == user
19
+ Rails.cache.read("users/#{user.id}").should == user
20
+ end
21
+
22
+ it "should get cached by User#id multiple times" do
23
+ User.find_cached(user.id)
24
+ User.find_cached(user.id).should == user
25
+ end
26
+ end
27
+
28
+ end