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.
- checksums.yaml +7 -0
- data/.gitignore +0 -1
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/ChangeLog +7 -0
- data/Gemfile +2 -4
- data/Gemfile.lock +115 -0
- data/README.md +1 -1
- data/lib/cacheable/caches.rb +15 -0
- data/lib/cacheable/expiry.rb +53 -0
- data/lib/cacheable/keys.rb +54 -0
- data/lib/cacheable/types/association_cache.rb +92 -0
- data/lib/cacheable/types/attribute_cache.rb +30 -0
- data/lib/cacheable/types/class_method_cache.rb +23 -0
- data/lib/cacheable/types/key_cache.rb +17 -0
- data/lib/cacheable/types/method_cache.rb +19 -0
- data/lib/cacheable/version.rb +1 -1
- data/lib/cacheable.rb +14 -263
- data/spec/cacheable/expiry_cache_spec.rb +81 -0
- data/spec/cacheable/types/association_cache_spec.rb +166 -0
- data/spec/cacheable/types/attribute_cache_spec.rb +60 -0
- data/spec/cacheable/types/class_method_cache_spec.rb +43 -0
- data/spec/cacheable/types/key_cache_spec.rb +28 -0
- data/spec/cacheable/types/method_cache_spec.rb +33 -0
- data/spec/cacheable_spec.rb +18 -292
- data/spec/models/post.rb +10 -1
- data/spec/spec_helper.rb +0 -1
- metadata +37 -22
- data/.rvmrc +0 -2
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b46d1110354d90d106fe5abdfed1eaa6801dd7e4
|
4
|
+
data.tar.gz: 763dac9a0c34135095974d09bb2575fbc5106ed2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f0fd40d41856c6efb7ecdfa1d8263ab2178b98c4cc8656d5bde2f3e67d71029c8cdebacf72a9d127abd775867d1d452fe86e8b122ec8f8adb315b9fab444999b
|
7
|
+
data.tar.gz: 322aad0ed21db8769cc06557df6461f8e8a29ddad18597922a4410480dc71a3d639217ea07a88b3c861db899802b4cf05c615242dc5069a9aab55325bfb0983a
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
cacheable
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0
|
data/ChangeLog
ADDED
data/Gemfile
CHANGED
@@ -4,7 +4,7 @@ source "http://rubygems.org"
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
platforms :ruby do
|
7
|
-
gem "sqlite3
|
7
|
+
gem "sqlite3"
|
8
8
|
gem "memcached"
|
9
9
|
end
|
10
10
|
|
@@ -12,6 +12,4 @@ platforms :jruby do
|
|
12
12
|
gem "activerecord-jdbc-adapter"
|
13
13
|
gem "activerecord-jdbcsqlite3-adapter"
|
14
14
|
gem "jruby-memcached"
|
15
|
-
end
|
16
|
-
|
17
|
-
gem "debugger"
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
simple_cacheable (1.3.2)
|
5
|
+
rails (>= 3.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionmailer (4.0.0)
|
11
|
+
actionpack (= 4.0.0)
|
12
|
+
mail (~> 2.5.3)
|
13
|
+
actionpack (4.0.0)
|
14
|
+
activesupport (= 4.0.0)
|
15
|
+
builder (~> 3.1.0)
|
16
|
+
erubis (~> 2.7.0)
|
17
|
+
rack (~> 1.5.2)
|
18
|
+
rack-test (~> 0.6.2)
|
19
|
+
activemodel (4.0.0)
|
20
|
+
activesupport (= 4.0.0)
|
21
|
+
builder (~> 3.1.0)
|
22
|
+
activerecord (4.0.0)
|
23
|
+
activemodel (= 4.0.0)
|
24
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
25
|
+
activesupport (= 4.0.0)
|
26
|
+
arel (~> 4.0.0)
|
27
|
+
activerecord-deprecated_finders (1.0.3)
|
28
|
+
activerecord-jdbc-adapter (1.2.2.1)
|
29
|
+
activerecord-jdbcsqlite3-adapter (1.2.2.1)
|
30
|
+
activerecord-jdbc-adapter (~> 1.2.2.1)
|
31
|
+
jdbc-sqlite3 (~> 3.7.2)
|
32
|
+
activesupport (4.0.0)
|
33
|
+
i18n (~> 0.6, >= 0.6.4)
|
34
|
+
minitest (~> 4.2)
|
35
|
+
multi_json (~> 1.3)
|
36
|
+
thread_safe (~> 0.1)
|
37
|
+
tzinfo (~> 0.3.37)
|
38
|
+
arel (4.0.0)
|
39
|
+
atomic (1.1.10)
|
40
|
+
atomic (1.1.10-java)
|
41
|
+
builder (3.1.4)
|
42
|
+
diff-lcs (1.1.3)
|
43
|
+
erubis (2.7.0)
|
44
|
+
hike (1.2.3)
|
45
|
+
i18n (0.6.4)
|
46
|
+
jdbc-sqlite3 (3.7.2)
|
47
|
+
jruby-memcached (0.5.1)
|
48
|
+
mail (2.5.4)
|
49
|
+
mime-types (~> 1.16)
|
50
|
+
treetop (~> 1.4.8)
|
51
|
+
memcached (1.4.6)
|
52
|
+
metaclass (0.0.1)
|
53
|
+
mime-types (1.23)
|
54
|
+
minitest (4.7.5)
|
55
|
+
mocha (0.10.5)
|
56
|
+
metaclass (~> 0.0.1)
|
57
|
+
multi_json (1.7.7)
|
58
|
+
polyglot (0.3.3)
|
59
|
+
rack (1.5.2)
|
60
|
+
rack-test (0.6.2)
|
61
|
+
rack (>= 1.0)
|
62
|
+
rails (4.0.0)
|
63
|
+
actionmailer (= 4.0.0)
|
64
|
+
actionpack (= 4.0.0)
|
65
|
+
activerecord (= 4.0.0)
|
66
|
+
activesupport (= 4.0.0)
|
67
|
+
bundler (>= 1.3.0, < 2.0)
|
68
|
+
railties (= 4.0.0)
|
69
|
+
sprockets-rails (~> 2.0.0)
|
70
|
+
railties (4.0.0)
|
71
|
+
actionpack (= 4.0.0)
|
72
|
+
activesupport (= 4.0.0)
|
73
|
+
rake (>= 0.8.7)
|
74
|
+
thor (>= 0.18.1, < 2.0)
|
75
|
+
rake (10.1.0)
|
76
|
+
rspec (2.8.0)
|
77
|
+
rspec-core (~> 2.8.0)
|
78
|
+
rspec-expectations (~> 2.8.0)
|
79
|
+
rspec-mocks (~> 2.8.0)
|
80
|
+
rspec-core (2.8.0)
|
81
|
+
rspec-expectations (2.8.0)
|
82
|
+
diff-lcs (~> 1.1.2)
|
83
|
+
rspec-mocks (2.8.0)
|
84
|
+
sprockets (2.10.0)
|
85
|
+
hike (~> 1.2)
|
86
|
+
multi_json (~> 1.0)
|
87
|
+
rack (~> 1.0)
|
88
|
+
tilt (~> 1.1, != 1.3.0)
|
89
|
+
sprockets-rails (2.0.0)
|
90
|
+
actionpack (>= 3.0)
|
91
|
+
activesupport (>= 3.0)
|
92
|
+
sprockets (~> 2.8)
|
93
|
+
sqlite3 (1.3.7)
|
94
|
+
thor (0.18.1)
|
95
|
+
thread_safe (0.1.0)
|
96
|
+
atomic
|
97
|
+
tilt (1.4.1)
|
98
|
+
treetop (1.4.14)
|
99
|
+
polyglot
|
100
|
+
polyglot (>= 0.3.1)
|
101
|
+
tzinfo (0.3.37)
|
102
|
+
|
103
|
+
PLATFORMS
|
104
|
+
java
|
105
|
+
ruby
|
106
|
+
|
107
|
+
DEPENDENCIES
|
108
|
+
activerecord-jdbc-adapter
|
109
|
+
activerecord-jdbcsqlite3-adapter
|
110
|
+
jruby-memcached
|
111
|
+
memcached
|
112
|
+
mocha
|
113
|
+
rspec
|
114
|
+
simple_cacheable!
|
115
|
+
sqlite3
|
data/README.md
CHANGED
@@ -76,7 +76,7 @@ Gotchas
|
|
76
76
|
|
77
77
|
Caching, and caching invalidation specifically, can be hard and confusing. Simple Cacheable methods should
|
78
78
|
expire correctly in most cases. Be careful using `with_method` and `with_class_method`, they should
|
79
|
-
specifically not be used to return collections. This is demonstrated well Tobias Lutke's presentation: [Rockstar Memcaching][2].
|
79
|
+
specifically not be used to return collections. This is demonstrated well in Tobias Lutke's presentation: [Rockstar Memcaching][2].
|
80
80
|
|
81
81
|
Copyright © 2011 Richard Huang (flyerhzm@gmail.com), released under the MIT license
|
82
82
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "cacheable/types/key_cache"
|
2
|
+
require "cacheable/types/attribute_cache"
|
3
|
+
require "cacheable/types/method_cache"
|
4
|
+
require "cacheable/types/class_method_cache"
|
5
|
+
require "cacheable/types/association_cache"
|
6
|
+
|
7
|
+
module Cacheable
|
8
|
+
module Caches
|
9
|
+
include Cacheable::KeyCache
|
10
|
+
include Cacheable::AttributeCache
|
11
|
+
include Cacheable::MethodCache
|
12
|
+
include Cacheable::ClassMethodCache
|
13
|
+
include Cacheable::AssocationCache
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Cacheable
|
2
|
+
module Expiry
|
3
|
+
def expire_model_cache
|
4
|
+
expire_key_cache if self.class.cached_key
|
5
|
+
expire_attribute_cache if self.class.cached_indices.present?
|
6
|
+
expire_all_attribute_cache if self.class.cached_indices.present?
|
7
|
+
expire_method_cache if self.class.cached_methods.present?
|
8
|
+
expire_class_method_cache if self.class.cached_class_methods.present?
|
9
|
+
|
10
|
+
if self.class.cached_associations.present?
|
11
|
+
self.class.cached_associations.each do |assoc|
|
12
|
+
expire_association_cache(assoc)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def expire_key_cache
|
18
|
+
Rails.cache.delete model_cache_key
|
19
|
+
end
|
20
|
+
|
21
|
+
def expire_attribute_cache
|
22
|
+
self.class.cached_indices.each do |attribute, values|
|
23
|
+
value = self.send(attribute)
|
24
|
+
Rails.cache.delete self.class.attribute_cache_key(attribute, value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def expire_all_attribute_cache
|
29
|
+
self.class.cached_indices.each do |attribute, values|
|
30
|
+
value = self.send(attribute)
|
31
|
+
Rails.cache.delete self.class.all_attribute_cache_key(attribute, value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def expire_method_cache
|
36
|
+
self.class.cached_methods.each do |meth|
|
37
|
+
Rails.cache.delete method_cache_key(meth)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def expire_class_method_cache
|
42
|
+
self.class.cached_class_methods.each do |meth, args|
|
43
|
+
args.each do |arg|
|
44
|
+
Rails.cache.delete self.class.class_method_cache_key(meth, arg)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def expire_association_cache(name)
|
50
|
+
Rails.cache.delete have_association_cache_key(name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Cacheable
|
2
|
+
module Keys
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(Cacheable::Keys::ClassKeys)
|
6
|
+
base.send :include, Cacheable::Keys::InstanceKeys
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassKeys
|
10
|
+
|
11
|
+
def attribute_cache_key(attribute, value)
|
12
|
+
"#{name.tableize}/attribute/#{attribute}/#{URI.escape(value.to_s)}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def all_attribute_cache_key(attribute, value)
|
16
|
+
"#{name.tableize}/attribute/#{attribute}/all/#{URI.escape(value.to_s)}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def class_method_cache_key(meth, *args)
|
20
|
+
key = "#{name.tableize}/class_method/#{meth}"
|
21
|
+
args.flatten!
|
22
|
+
key += "/#{args.join('+')}" if args.any?
|
23
|
+
return key
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
module InstanceKeys
|
29
|
+
|
30
|
+
def model_cache_key
|
31
|
+
"#{self.class.name.tableize}/#{self.id.to_i}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_cache_key(meth)
|
35
|
+
"#{model_cache_key}/method/#{meth}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def belong_association_cache_key(name, polymorphic=nil)
|
39
|
+
name = name.to_s if name.is_a?(Symbol)
|
40
|
+
if polymorphic
|
41
|
+
"#{self.send("#{name}_type").tableize}/#{self.send("#{name}_id")}"
|
42
|
+
else
|
43
|
+
"#{name.tableize}/#{self.send(name + "_id")}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def have_association_cache_key(name)
|
48
|
+
"#{model_cache_key}/association/#{name}"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Cacheable
|
2
|
+
module AssocationCache
|
3
|
+
|
4
|
+
def with_association(*association_names)
|
5
|
+
self.cached_associations = association_names
|
6
|
+
|
7
|
+
association_names.each do |association_name|
|
8
|
+
association = reflect_on_association(association_name)
|
9
|
+
|
10
|
+
if :belongs_to == association.macro
|
11
|
+
polymorphic = association.options[:polymorphic]
|
12
|
+
polymorphic ||= false
|
13
|
+
|
14
|
+
define_method("cached_#{association_name}") do
|
15
|
+
Rails.cache.fetch belong_association_cache_key(association_name, polymorphic) do
|
16
|
+
send(association_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
else
|
20
|
+
if through_reflection_name = association.options[:through]
|
21
|
+
through_association = self.reflect_on_association(through_reflection_name)
|
22
|
+
|
23
|
+
# FIXME it should be the only reflection but I'm not 100% positive
|
24
|
+
reverse_through_association = through_association.klass.reflect_on_all_associations(:belongs_to).first
|
25
|
+
|
26
|
+
# In a through association it doesn't have to be a belongs_to
|
27
|
+
reverse_association = association.klass.reflect_on_all_associations(:belongs_to).find { |reverse_association|
|
28
|
+
reverse_association.options[:polymorphic] ? reverse_association.name == association.source_reflection.options[:as] : reverse_association.klass == self
|
29
|
+
}
|
30
|
+
if reverse_association
|
31
|
+
association.klass.class_eval do
|
32
|
+
after_commit "expire_#{association_name}_cache".to_sym
|
33
|
+
|
34
|
+
define_method("expire_#{association_name}_cache") do
|
35
|
+
if respond_to? "expire_#{reverse_association.name}_cache".to_sym
|
36
|
+
# cached_viewable.expire_association_cache
|
37
|
+
send("cached_#{reverse_association.name}").expire_association_cache(association_name)
|
38
|
+
else
|
39
|
+
send(reverse_association.name).send(reverse_through_association.name).expire_association_cache(association_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
elsif :has_and_belongs_to_many == association.macro
|
45
|
+
# No such thing as a polymorphic has_and_belongs_to_many
|
46
|
+
reverse_association = association.klass.reflect_on_all_associations(:has_and_belongs_to_many).find { |reverse_association|
|
47
|
+
reverse_association.klass == self
|
48
|
+
}
|
49
|
+
|
50
|
+
association.klass.class_eval do
|
51
|
+
after_commit "expire_#{association_name}_cache".to_sym
|
52
|
+
|
53
|
+
define_method "expire_#{association_name}_cache" do
|
54
|
+
if respond_to? "cached_#{reverse_association.name}".to_sym
|
55
|
+
# cached_viewable.expire_association_cache
|
56
|
+
send("cached_#{reverse_association.name}").expire_association_cache(association_name)
|
57
|
+
else
|
58
|
+
send("#{reverse_association.name}").each do |assoc|
|
59
|
+
assoc.expire_association_cache(association_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
else
|
65
|
+
reverse_association = association.klass.reflect_on_all_associations(:belongs_to).find { |reverse_association|
|
66
|
+
reverse_association.options[:polymorphic] ? reverse_association.name == association.options[:as] : reverse_association.klass == self
|
67
|
+
}
|
68
|
+
|
69
|
+
association.klass.class_eval do
|
70
|
+
after_commit "expire_#{association_name}_cache".to_sym
|
71
|
+
|
72
|
+
define_method "expire_#{association_name}_cache" do
|
73
|
+
if respond_to? "cached_#{reverse_association.name}".to_sym
|
74
|
+
send("cached_#{reverse_association.name}").expire_association_cache(association_name)
|
75
|
+
else
|
76
|
+
send("#{reverse_association.name}").expire_association_cache(association_name)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
define_method("cached_#{association_name}") do
|
83
|
+
Rails.cache.fetch have_association_cache_key(association_name) do
|
84
|
+
send(association_name).respond_to?(:to_a) ? send(association_name).to_a : send(association_name)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Cacheable
|
2
|
+
module AttributeCache
|
3
|
+
def with_attribute(*attributes)
|
4
|
+
self.cached_indices = attributes.inject({}) { |indices, attribute| indices[attribute] = {} }
|
5
|
+
|
6
|
+
class_eval do
|
7
|
+
after_commit :expire_attribute_cache, :on => :update
|
8
|
+
after_commit :expire_all_attribute_cache, :on => :update
|
9
|
+
end
|
10
|
+
|
11
|
+
attributes.each do |attribute|
|
12
|
+
define_singleton_method("find_cached_by_#{attribute}") do |value|
|
13
|
+
self.cached_indices["#{attribute}"] ||= []
|
14
|
+
self.cached_indices["#{attribute}"] << value
|
15
|
+
Rails.cache.fetch attribute_cache_key("#{attribute}", value) do
|
16
|
+
self.send("find_by_#{attribute}", value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
define_singleton_method("find_cached_all_by_#{attribute}") do |value|
|
21
|
+
self.cached_indices["#{attribute}"] ||= []
|
22
|
+
self.cached_indices["#{attribute}"] << value
|
23
|
+
Rails.cache.fetch all_attribute_cache_key("#{attribute}", value) do
|
24
|
+
self.send("find_all_by_#{attribute}", value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Cacheable
|
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 = methods.inject({}) { |indices, meth| indices[meth] = {} }
|
7
|
+
|
8
|
+
class_eval do
|
9
|
+
after_commit :expire_class_method_cache, on: :update
|
10
|
+
end
|
11
|
+
|
12
|
+
methods.each do |meth|
|
13
|
+
define_singleton_method("cached_#{meth}") do |*args|
|
14
|
+
self.cached_class_methods["#{meth}"] ||= []
|
15
|
+
self.cached_class_methods["#{meth}"] << args
|
16
|
+
Rails.cache.fetch class_method_cache_key(meth, args) do
|
17
|
+
self.method(meth).arity == 0 ? send(meth) : send(meth, *args)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Cacheable
|
2
|
+
module KeyCache
|
3
|
+
def with_key
|
4
|
+
self.cached_key = true
|
5
|
+
|
6
|
+
class_eval do
|
7
|
+
after_commit :expire_key_cache, on: :update
|
8
|
+
end
|
9
|
+
|
10
|
+
define_singleton_method("find_cached") do |id|
|
11
|
+
Rails.cache.fetch "#{name.tableize}/" + id.to_i.to_s do
|
12
|
+
self.find(id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cacheable
|
2
|
+
module MethodCache
|
3
|
+
def with_method(*methods)
|
4
|
+
self.cached_methods = methods
|
5
|
+
|
6
|
+
class_eval do
|
7
|
+
after_commit :expire_method_cache, :on => :update
|
8
|
+
end
|
9
|
+
|
10
|
+
methods.each do |meth|
|
11
|
+
define_method("cached_#{meth}") do
|
12
|
+
Rails.cache.fetch method_cache_key(meth) do
|
13
|
+
send(meth)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/cacheable/version.rb
CHANGED