simple_cacheable 1.4.1 → 1.5.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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -2
- data/Appraisals +7 -0
- data/ChangeLog +9 -0
- data/Gemfile +10 -9
- data/Gemfile.lock +44 -46
- data/README.md +10 -0
- data/Rakefile +2 -0
- data/cacheable.gemspec +4 -2
- data/gemfiles/3.2.gemfile +10 -0
- data/gemfiles/3.2.gemfile.lock +117 -0
- data/gemfiles/4.0.gemfile +10 -0
- data/gemfiles/4.0.gemfile.lock +112 -0
- data/lib/cacheable.rb +11 -0
- data/lib/cacheable/expiry.rb +3 -1
- data/lib/cacheable/keys.rb +29 -7
- data/lib/cacheable/model_fetch.rb +59 -0
- data/lib/cacheable/types/association_cache.rb +128 -73
- data/lib/cacheable/types/attribute_cache.rb +7 -3
- data/lib/cacheable/types/class_method_cache.rb +2 -2
- data/lib/cacheable/types/key_cache.rb +2 -1
- data/lib/cacheable/types/method_cache.rb +10 -3
- data/lib/cacheable/version.rb +1 -1
- data/spec/cacheable/expiry_cache_spec.rb +44 -45
- data/spec/cacheable/model_fetch_spec.rb +86 -0
- data/spec/cacheable/types/association_cache_spec.rb +267 -43
- data/spec/cacheable/types/attribute_cache_spec.rb +31 -24
- data/spec/cacheable/types/class_method_cache_spec.rb +49 -21
- data/spec/cacheable/types/key_cache_spec.rb +51 -12
- data/spec/cacheable/types/method_cache_spec.rb +76 -37
- data/spec/cacheable_spec.rb +19 -20
- data/spec/models/account.rb +8 -0
- data/spec/models/post.rb +21 -1
- data/spec/models/user.rb +38 -2
- data/spec/spec_helper.rb +13 -0
- data/spec/support/ar_patches.rb +51 -0
- data/spec/support/coder_macro.rb +12 -0
- metadata +34 -7
data/lib/cacheable.rb
CHANGED
@@ -2,13 +2,17 @@ require 'uri'
|
|
2
2
|
require "cacheable/caches"
|
3
3
|
require "cacheable/keys"
|
4
4
|
require "cacheable/expiry"
|
5
|
+
require "cacheable/model_fetch"
|
5
6
|
|
6
7
|
module Cacheable
|
8
|
+
extend ModelFetch
|
9
|
+
|
7
10
|
def self.included(base)
|
8
11
|
base.extend(Cacheable::Caches)
|
9
12
|
base.send :include, Cacheable::Keys
|
10
13
|
base.send :include, Cacheable::Expiry
|
11
14
|
base.send :extend, ClassMethods
|
15
|
+
|
12
16
|
base.class_eval do
|
13
17
|
class_attribute :cached_key,
|
14
18
|
:cached_indices,
|
@@ -16,7 +20,14 @@ module Cacheable
|
|
16
20
|
:cached_class_methods,
|
17
21
|
:cached_associations
|
18
22
|
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.escape_punctuation(string)
|
26
|
+
string.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')
|
27
|
+
end
|
19
28
|
|
29
|
+
def self.rails4?
|
30
|
+
ActiveRecord::VERSION::MAJOR >= 4
|
20
31
|
end
|
21
32
|
|
22
33
|
module ClassMethods
|
data/lib/cacheable/expiry.rb
CHANGED
data/lib/cacheable/keys.rb
CHANGED
@@ -9,38 +9,49 @@ module Cacheable
|
|
9
9
|
module ClassKeys
|
10
10
|
|
11
11
|
def attribute_cache_key(attribute, value)
|
12
|
-
"#{name.tableize}/attribute/#{attribute}/#{URI.escape(value.to_s)}"
|
12
|
+
"#{self.base_class.name.tableize}/attribute/#{attribute}/#{URI.escape(value.to_s)}"
|
13
13
|
end
|
14
14
|
|
15
15
|
def all_attribute_cache_key(attribute, value)
|
16
|
-
"#{name.tableize}/attribute/#{attribute}/all/#{URI.escape(value.to_s)}"
|
16
|
+
"#{self.base_class.name.tableize}/attribute/#{attribute}/all/#{URI.escape(value.to_s)}"
|
17
17
|
end
|
18
18
|
|
19
19
|
def class_method_cache_key(meth, *args)
|
20
|
-
key = "#{name.tableize}/class_method/#{meth}"
|
20
|
+
key = "#{self.base_class.name.tableize}/class_method/#{meth}"
|
21
21
|
args.flatten!
|
22
22
|
key += "/#{args.join('+')}" if args.any?
|
23
23
|
return key
|
24
24
|
end
|
25
25
|
|
26
|
+
def instance_cache_key(param)
|
27
|
+
"#{self.base_class.name.tableize}/#{param}"
|
28
|
+
end
|
29
|
+
|
26
30
|
end
|
27
31
|
|
28
32
|
module InstanceKeys
|
29
33
|
|
34
|
+
def model_cache_keys
|
35
|
+
["#{self.class.base_class.name.tableize}/#{self.id.to_i}", "#{self.class.base_class.name.tableize}/#{self.to_param}"]
|
36
|
+
end
|
37
|
+
|
30
38
|
def model_cache_key
|
31
|
-
"#{self.class.name.tableize}/#{self.id.to_i}"
|
39
|
+
"#{self.class.base_class.name.tableize}/#{self.id.to_i}"
|
32
40
|
end
|
33
41
|
|
34
42
|
def method_cache_key(meth)
|
35
43
|
"#{model_cache_key}/method/#{meth}"
|
36
44
|
end
|
37
45
|
|
46
|
+
# Returns nil if association cannot be qualified
|
38
47
|
def belong_association_cache_key(name, polymorphic=nil)
|
39
48
|
name = name.to_s if name.is_a?(Symbol)
|
40
|
-
|
41
|
-
|
49
|
+
|
50
|
+
if polymorphic && self.respond_to?(:"#{name}_type")
|
51
|
+
return nil unless self.send(:"#{name}_type").present?
|
52
|
+
"#{base_class_or_name(self.send(:"#{name}_type"))}/#{self.send(:"#{name}_id")}"
|
42
53
|
else
|
43
|
-
"#{name
|
54
|
+
"#{base_class_or_name(name)}/#{self.send(:"#{name}_id")}"
|
44
55
|
end
|
45
56
|
end
|
46
57
|
|
@@ -48,6 +59,17 @@ module Cacheable
|
|
48
59
|
"#{model_cache_key}/association/#{name}"
|
49
60
|
end
|
50
61
|
|
62
|
+
# If it isa class. It should be the base_class name
|
63
|
+
# else it should just be a name tableized
|
64
|
+
def base_class_or_name(name)
|
65
|
+
name = begin
|
66
|
+
name.capitalize.constantize.base_class.name
|
67
|
+
rescue NameError # uninitialized constant
|
68
|
+
name
|
69
|
+
end
|
70
|
+
name.tableize
|
71
|
+
end
|
72
|
+
|
51
73
|
end
|
52
74
|
|
53
75
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Cacheable
|
2
|
+
module ModelFetch
|
3
|
+
def fetch(key, options=nil)
|
4
|
+
unless result = read(key, options)
|
5
|
+
if block_given?
|
6
|
+
result = yield
|
7
|
+
write(key, result, options) unless result.nil?
|
8
|
+
end
|
9
|
+
end
|
10
|
+
result
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def write(key, value, options=nil)
|
16
|
+
options ||= {}
|
17
|
+
|
18
|
+
coder = if !value.is_a?(Hash) && value.respond_to?(:to_a)
|
19
|
+
value.to_a.map {|obj| coder_from_record(obj) }
|
20
|
+
else
|
21
|
+
coder_from_record(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
Rails.cache.write(key, coder, options)
|
25
|
+
coder
|
26
|
+
end
|
27
|
+
|
28
|
+
def read(key, options=nil)
|
29
|
+
options ||= {}
|
30
|
+
value = Rails.cache.read(key, options)
|
31
|
+
return nil if value.nil?
|
32
|
+
|
33
|
+
if !coder?(value) && value.respond_to?(:to_a)
|
34
|
+
value.to_a.map { |obj| record_from_coder(obj) }
|
35
|
+
else
|
36
|
+
record_from_coder(value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def coder_from_record(record)
|
41
|
+
return if record.nil?
|
42
|
+
return record unless record.is_a?(ActiveRecord::Base)
|
43
|
+
|
44
|
+
coder = { :class => record.class }
|
45
|
+
record.encode_with(coder)
|
46
|
+
coder
|
47
|
+
end
|
48
|
+
|
49
|
+
def record_from_coder(coder)
|
50
|
+
return coder unless coder?(coder)
|
51
|
+
record = coder[:class].allocate
|
52
|
+
record.init_with(coder)
|
53
|
+
end
|
54
|
+
|
55
|
+
def coder?(value)
|
56
|
+
value.is_a?(Hash) && value[:class].present?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -9,92 +9,147 @@ module Cacheable
|
|
9
9
|
association = reflect_on_association(association_name)
|
10
10
|
|
11
11
|
if :belongs_to == association.macro
|
12
|
-
|
13
|
-
polymorphic ||= false
|
14
|
-
|
15
|
-
define_method("cached_#{association_name}") do
|
16
|
-
Rails.cache.fetch belong_association_cache_key(association_name, polymorphic) do
|
17
|
-
send(association_name)
|
18
|
-
end
|
19
|
-
end
|
12
|
+
build_cache_belongs_to(association, association_name)
|
20
13
|
else
|
14
|
+
|
21
15
|
if through_reflection_name = association.options[:through]
|
22
|
-
|
16
|
+
build_cache_has_through(association, association_name, through_reflection_name)
|
17
|
+
elsif :has_and_belongs_to_many == association.macro
|
18
|
+
build_cache_has_and_belongs_to_many(association, association_name)
|
19
|
+
else
|
20
|
+
build_cache_has_many(association, association_name)
|
21
|
+
end
|
23
22
|
|
24
|
-
|
25
|
-
|
23
|
+
method_name = :"cached_#{association_name}"
|
24
|
+
define_method(method_name) do
|
25
|
+
if instance_variable_get("@#{method_name}").nil?
|
26
|
+
association_cache.delete(association_name)
|
27
|
+
cache_key = have_association_cache_key(association_name)
|
28
|
+
result = Cacheable.fetch(cache_key) do
|
29
|
+
send(association_name)
|
30
|
+
end
|
31
|
+
instance_variable_set("@#{method_name}", result)
|
26
32
|
end
|
33
|
+
instance_variable_get("@#{method_name}")
|
34
|
+
end
|
27
35
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# No expiring callback
|
42
|
+
def build_cache_belongs_to(association, association_name)
|
43
|
+
polymorphic = association.options[:polymorphic]
|
44
|
+
polymorphic ||= false
|
45
|
+
|
46
|
+
method_name = association_name
|
47
|
+
cached_method_name = :"cached_#{association_name}"
|
48
|
+
|
49
|
+
define_method(cached_method_name) do
|
50
|
+
if instance_variable_get("@#{cached_method_name}").nil?
|
51
|
+
cache_key = belong_association_cache_key(association_name, polymorphic)
|
52
|
+
result = if cache_key
|
53
|
+
association_cache.delete(association_name)
|
54
|
+
Cacheable.fetch(cache_key) do
|
55
|
+
send(association_name)
|
49
56
|
end
|
50
|
-
elsif :has_and_belongs_to_many == association.macro
|
51
|
-
# No such thing as a polymorphic has_and_belongs_to_many
|
52
|
-
reverse_association = association.klass.reflect_on_all_associations(:has_and_belongs_to_many).find { |reverse_association|
|
53
|
-
reverse_association.klass == self
|
54
|
-
}
|
55
|
-
|
56
|
-
association.klass.class_eval do
|
57
|
-
after_commit "expire_#{association_name}_cache".to_sym
|
58
|
-
|
59
|
-
define_method "expire_#{association_name}_cache" do
|
60
|
-
if respond_to? "cached_#{reverse_association.name}".to_sym
|
61
|
-
unless send("cached_#{reverse_association.name}").nil?
|
62
|
-
# cached_viewable.expire_association_cache
|
63
|
-
send("cached_#{reverse_association.name}").expire_association_cache(association_name)
|
64
|
-
end
|
65
|
-
elsif !send("#{reverse_association.name}").nil?
|
66
|
-
send("#{reverse_association.name}").each do |assoc|
|
67
|
-
next if assoc.nil?
|
68
|
-
assoc.expire_association_cache(association_name)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
57
|
else
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
58
|
+
# Should be nil, but preserve functionality
|
59
|
+
send(association_name)
|
60
|
+
end
|
61
|
+
instance_variable_set("@#{cached_method_name}", result)
|
62
|
+
end
|
63
|
+
instance_variable_get("@#{cached_method_name}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_cache_has_through(association, association_name, through_reflection_name)
|
68
|
+
through_association = self.reflect_on_association(through_reflection_name)
|
69
|
+
|
70
|
+
reverse_through_association = through_association.klass.reflect_on_all_associations(:belongs_to).detect do |assoc|
|
71
|
+
assoc.klass.ancestors.include?(Cacheable) && assoc.klass.reflect_on_association(association.name)
|
72
|
+
end
|
73
|
+
|
74
|
+
# In a through association it doesn't have to be a belongs_to
|
75
|
+
reverse_association = association.klass.reflect_on_all_associations(:belongs_to).find { |reverse_association|
|
76
|
+
reverse_association.options[:polymorphic] ? reverse_association.name == association.source_reflection.options[:as] : reverse_association.klass == self
|
77
|
+
}
|
78
|
+
if reverse_association
|
79
|
+
association.klass.class_eval do
|
80
|
+
after_commit :"expire_#{association_name}_cache"
|
81
|
+
|
82
|
+
define_method(:"expire_#{association_name}_cache") do
|
83
|
+
|
84
|
+
method_name = reverse_association.name
|
85
|
+
cached_method_name = :"cached_#{reverse_association.name}"
|
86
|
+
|
87
|
+
if respond_to? cached_method_name
|
88
|
+
unless send(cached_method_name).nil?
|
89
|
+
send(cached_method_name).expire_association_cache(association_name)
|
90
|
+
end
|
91
|
+
elsif !send(method_name).nil?
|
92
|
+
if send(method_name).respond_to?(reverse_through_association.name) && !send(method_name).send(reverse_through_association.name).nil?
|
93
|
+
send(method_name).send(reverse_through_association.name).expire_association_cache(association_name)
|
89
94
|
end
|
90
95
|
end
|
96
|
+
|
91
97
|
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def build_cache_has_and_belongs_to_many(association, association_name)
|
103
|
+
# No such thing as a polymorphic has_and_belongs_to_many
|
104
|
+
reverse_association = association.klass.reflect_on_all_associations(:has_and_belongs_to_many).find { |reverse_association|
|
105
|
+
reverse_association.klass == self
|
106
|
+
}
|
107
|
+
|
108
|
+
association.klass.class_eval do
|
109
|
+
after_commit :"expire_#{association_name}_cache"
|
110
|
+
|
111
|
+
define_method :"expire_#{association_name}_cache" do
|
112
|
+
|
113
|
+
method_name = :"#{reverse_association.name}"
|
114
|
+
cached_method_name = :"cached_#{reverse_association.name}"
|
92
115
|
|
93
|
-
|
94
|
-
|
95
|
-
|
116
|
+
if respond_to? cached_method_name
|
117
|
+
unless send(cached_method_name).nil?
|
118
|
+
# cached_viewable.expire_association_cache
|
119
|
+
send(cached_method_name).expire_association_cache(association_name)
|
120
|
+
end
|
121
|
+
elsif !send(method_name).nil?
|
122
|
+
send(method_name).each do |assoc|
|
123
|
+
next if assoc.nil?
|
124
|
+
assoc.expire_association_cache(association_name)
|
96
125
|
end
|
97
126
|
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def build_cache_has_many(association, association_name)
|
133
|
+
reverse_association = association.klass.reflect_on_all_associations(:belongs_to).find { |reverse_association|
|
134
|
+
reverse_association.options[:polymorphic] ? reverse_association.name == association.options[:as] : reverse_association.klass == self
|
135
|
+
}
|
136
|
+
|
137
|
+
association.klass.class_eval do
|
138
|
+
after_commit :"expire_#{association_name}_cache"
|
139
|
+
|
140
|
+
define_method :"expire_#{association_name}_cache" do
|
141
|
+
|
142
|
+
cached_method_name = :"cached_#{reverse_association.name}"
|
143
|
+
method_name = :"#{reverse_association.name}"
|
144
|
+
|
145
|
+
if respond_to? cached_method_name
|
146
|
+
unless send(cached_method_name).nil?
|
147
|
+
send(cached_method_name).expire_association_cache(association_name)
|
148
|
+
end
|
149
|
+
elsif !send(method_name).nil?
|
150
|
+
send(method_name).expire_association_cache(association_name)
|
151
|
+
end
|
152
|
+
|
98
153
|
end
|
99
154
|
end
|
100
155
|
end
|
@@ -15,7 +15,7 @@ module Cacheable
|
|
15
15
|
define_singleton_method("find_cached_by_#{attribute}") do |value|
|
16
16
|
self.cached_indices["#{attribute}"] ||= []
|
17
17
|
self.cached_indices["#{attribute}"] << value
|
18
|
-
|
18
|
+
Cacheable.fetch(attribute_cache_key("#{attribute}", value)) do
|
19
19
|
self.send("find_by_#{attribute}", value)
|
20
20
|
end
|
21
21
|
end
|
@@ -23,8 +23,12 @@ module Cacheable
|
|
23
23
|
define_singleton_method("find_cached_all_by_#{attribute}") do |value|
|
24
24
|
self.cached_indices["#{attribute}"] ||= []
|
25
25
|
self.cached_indices["#{attribute}"] << value
|
26
|
-
|
27
|
-
|
26
|
+
Cacheable.fetch(all_attribute_cache_key("#{attribute}", value)) do
|
27
|
+
if Cacheable.rails4?
|
28
|
+
self.where("#{attribute}" => value).load
|
29
|
+
else
|
30
|
+
self.send("find_all_by_#{attribute}", value)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
end
|
30
34
|
end
|
@@ -3,7 +3,7 @@ module Cacheable
|
|
3
3
|
# Cached class method
|
4
4
|
# Should expire on any instance save
|
5
5
|
def with_class_method(*methods)
|
6
|
-
self.cached_class_methods = methods.each_with_object({}) { |meth, indices| indices[meth] =
|
6
|
+
self.cached_class_methods = methods.each_with_object({}) { |meth, indices| indices[meth] = [] }
|
7
7
|
|
8
8
|
class_eval do
|
9
9
|
after_commit :expire_class_method_cache, on: :update
|
@@ -13,7 +13,7 @@ module Cacheable
|
|
13
13
|
define_singleton_method("cached_#{meth}") do |*args|
|
14
14
|
self.cached_class_methods["#{meth}"] ||= []
|
15
15
|
self.cached_class_methods["#{meth}"] << args
|
16
|
-
|
16
|
+
Cacheable.fetch class_method_cache_key(meth, args) do
|
17
17
|
self.method(meth).arity == 0 ? send(meth) : send(meth, *args)
|
18
18
|
end
|
19
19
|
end
|