simple_cacheable 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -15,7 +15,9 @@ module Cacheable
15
15
  end
16
16
 
17
17
  def expire_key_cache
18
- Rails.cache.delete model_cache_key
18
+ model_cache_keys.each do |key|
19
+ Rails.cache.delete key
20
+ end
19
21
  end
20
22
 
21
23
  def expire_attribute_cache
@@ -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
- if polymorphic
41
- "#{self.send("#{name}_type").tableize}/#{self.send("#{name}_id")}"
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.tableize}/#{self.send(name + "_id")}"
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
- polymorphic = association.options[:polymorphic]
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
- through_association = self.reflect_on_association(through_reflection_name)
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
- reverse_through_association = through_association.klass.reflect_on_all_associations(:belongs_to).detect do |assoc|
25
- assoc.klass.ancestors.include?(Cacheable) && assoc.klass.reflect_on_association(association.name)
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
- # In a through association it doesn't have to be a belongs_to
29
- reverse_association = association.klass.reflect_on_all_associations(:belongs_to).find { |reverse_association|
30
- reverse_association.options[:polymorphic] ? reverse_association.name == association.source_reflection.options[:as] : reverse_association.klass == self
31
- }
32
- if reverse_association
33
- association.klass.class_eval do
34
- after_commit "expire_#{association_name}_cache".to_sym
35
-
36
- define_method("expire_#{association_name}_cache") do
37
-
38
- if respond_to? "expire_#{reverse_association.name}_cache".to_sym
39
- unless send("cached_#{reverse_association.name}").nil?
40
- send("cached_#{reverse_association.name}").expire_association_cache(association_name)
41
- end
42
- elsif !send(reverse_association.name).nil?
43
- if send(reverse_association.name).respond_to?(reverse_through_association.name) && !send(reverse_association.name).send(reverse_through_association.name).nil?
44
- send(reverse_association.name).send(reverse_through_association.name).expire_association_cache(association_name)
45
- end
46
- end
47
- end
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
- reverse_association = association.klass.reflect_on_all_associations(:belongs_to).find { |reverse_association|
75
- reverse_association.options[:polymorphic] ? reverse_association.name == association.options[:as] : reverse_association.klass == self
76
- }
77
-
78
- association.klass.class_eval do
79
- after_commit "expire_#{association_name}_cache".to_sym
80
-
81
- define_method "expire_#{association_name}_cache" do
82
- if respond_to? "cached_#{reverse_association.name}".to_sym
83
- unless send("cached_#{reverse_association.name}").nil?
84
- send("cached_#{reverse_association.name}").expire_association_cache(association_name)
85
- end
86
- elsif !send("#{reverse_association.name}").nil?
87
- send("#{reverse_association.name}").expire_association_cache(association_name)
88
- end
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
- define_method("cached_#{association_name}") do
94
- Rails.cache.fetch have_association_cache_key(association_name) do
95
- send(association_name).respond_to?(:to_a) ? send(association_name).to_a : send(association_name)
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
- Rails.cache.fetch attribute_cache_key("#{attribute}", value) do
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
- Rails.cache.fetch all_attribute_cache_key("#{attribute}", value) do
27
- self.send("find_all_by_#{attribute}", value)
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
- Rails.cache.fetch class_method_cache_key(meth, args) do
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
@@ -8,7 +8,8 @@ module Cacheable
8
8
  end
9
9
 
10
10
  define_singleton_method("find_cached") do |id|
11
- Rails.cache.fetch "#{name.tableize}/" + id.to_i.to_s do
11
+ cache_key = self.instance_cache_key(id)
12
+ Cacheable.fetch(cache_key) do
12
13
  self.find(id)
13
14
  end
14
15
  end