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.
@@ -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