active_model_cachers 2.1.7 → 2.1.8

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.
Files changed (35) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ruby.yml +59 -0
  3. data/.gitignore +9 -9
  4. data/CHANGELOG.md +71 -67
  5. data/CODE_OF_CONDUCT.md +48 -48
  6. data/LICENSE.txt +21 -21
  7. data/README.md +399 -390
  8. data/Rakefile +10 -10
  9. data/active_model_cachers.gemspec +45 -45
  10. data/bin/console +14 -14
  11. data/bin/setup +8 -8
  12. data/gemfiles/3.2.gemfile +11 -11
  13. data/gemfiles/4.2.gemfile +11 -11
  14. data/gemfiles/5.0.gemfile +11 -11
  15. data/gemfiles/5.1.gemfile +11 -11
  16. data/gemfiles/5.2.gemfile +11 -11
  17. data/gemfiles/6.0.gemfile +11 -11
  18. data/lib/active_model_cachers/active_record/attr_model.rb +124 -124
  19. data/lib/active_model_cachers/active_record/cacher.rb +97 -97
  20. data/lib/active_model_cachers/active_record/extension.rb +119 -119
  21. data/lib/active_model_cachers/active_record/global_callbacks.rb +67 -67
  22. data/lib/active_model_cachers/cache_service.rb +151 -151
  23. data/lib/active_model_cachers/cache_service_factory.rb +55 -55
  24. data/lib/active_model_cachers/column_value_cache.rb +47 -47
  25. data/lib/active_model_cachers/config.rb +6 -6
  26. data/lib/active_model_cachers/false_object.rb +5 -5
  27. data/lib/active_model_cachers/hook/associations.rb +43 -43
  28. data/lib/active_model_cachers/hook/dependencies.rb +38 -38
  29. data/lib/active_model_cachers/hook/on_model_delete.rb +29 -29
  30. data/lib/active_model_cachers/nil_object.rb +5 -5
  31. data/lib/active_model_cachers/patches/patch_rails_3.rb +49 -49
  32. data/lib/active_model_cachers/patches/uninitialized_attribute.rb +9 -9
  33. data/lib/active_model_cachers/version.rb +4 -4
  34. metadata +12 -7
  35. data/.travis.yml +0 -33
@@ -1,151 +1,151 @@
1
- # frozen_string_literal: true
2
- require 'active_model_cachers/nil_object'
3
- require 'active_model_cachers/false_object'
4
- require 'active_model_cachers/column_value_cache'
5
-
6
- module ActiveModelCachers
7
- class CacheService
8
- class << self
9
- attr_accessor :cache_key
10
- attr_accessor :query_mapping
11
-
12
- def instance(id)
13
- hash = (RequestStore.store[self] ||= {})
14
- return hash[id] ||= new(id)
15
- end
16
-
17
- def clean_at(id)
18
- instance(id).clean_cache
19
- end
20
-
21
- @@column_value_cache = ActiveModelCachers::ColumnValueCache.new
22
- def define_callback_for_cleaning_cache(class_name, column, foreign_key, with_id, on: nil)
23
- return if @callbacks_defined
24
- @callbacks_defined = true
25
-
26
- clean = ->(id){ clean_at(with_id ? id : nil) }
27
- clean_ids = []
28
- fire_on = Array(on) if on
29
-
30
- ActiveRecord::Extension.global_callbacks.instance_exec do
31
- on_nullify(class_name) do |nullified_column, get_ids|
32
- get_ids.call.each{|s| clean.call(s) } if nullified_column == column
33
- end
34
-
35
- after_touch1(class_name) do
36
- clean.call(@@column_value_cache.add(self.class, class_name, id, foreign_key, self).call)
37
- end
38
-
39
- after_touch2(class_name) do
40
- @@column_value_cache.clean_cache
41
- end
42
-
43
- after_commit1(class_name) do
44
- next if fire_on and not transaction_include_any_action?(fire_on)
45
- changed = column ? previous_changes.key?(column) : previous_changes.present?
46
- if changed || destroyed?
47
- clean.call(@@column_value_cache.add(self.class, class_name, id, foreign_key, self).call)
48
- end
49
- end
50
-
51
- after_commit2(class_name) do
52
- @@column_value_cache.clean_cache
53
- end
54
-
55
- before_delete1(class_name) do |id, model|
56
- clean_ids << @@column_value_cache.add(self, class_name, id, foreign_key, model)
57
- end
58
-
59
- before_delete2(class_name) do |_, model|
60
- clean_ids.each{|s| clean.call(s.call) }
61
- clean_ids = []
62
- end
63
-
64
- after_delete(class_name) do
65
- @@column_value_cache.clean_cache
66
- end
67
- end
68
- end
69
- end
70
-
71
- # ----------------------------------------------------------------
72
- # ● instance methods
73
- # ----------------------------------------------------------------
74
- def initialize(id)
75
- @id = id
76
- end
77
-
78
- def get(binding: nil, reflect: nil)
79
- @cached_data ||= fetch_from_cache(binding: binding, reflect: reflect)
80
- return cache_to_raw_data(@cached_data)
81
- end
82
-
83
- def peek(binding: nil, reflect: nil)
84
- @cached_data ||= get_from_cache
85
- return cache_to_raw_data(@cached_data)
86
- end
87
-
88
- def clean_cache(binding: nil, reflect: nil)
89
- @cached_data = nil
90
- Rails.cache.delete(cache_key)
91
- return nil
92
- end
93
-
94
- private
95
-
96
- def cache_key
97
- key = self.class.cache_key
98
- return @id ? "#{key}_#{@id}" : key
99
- end
100
-
101
- def get_query(binding, reflect)
102
- self.class.query_mapping[reflect] || begin
103
- puts "Warning: cannot find query. possible reflects: #{self.class.query_mapping.keys}, reflect: #{reflect}"
104
- self.class.query_mapping.values.first
105
- end
106
- end
107
-
108
- def get_without_cache(binding, attr)
109
- query = get_query(binding, attr)
110
- return binding ? binding.instance_exec(@id, &query) : query.call(@id) if @id and query.parameters.size == 1
111
- return binding ? binding.instance_exec(&query) : query.call
112
- end
113
-
114
- def raw_to_cache_data(raw)
115
- return NilObject if raw == nil
116
- return FalseObject if raw == false
117
- clean_ar_cache(raw.is_a?(Array) ? raw : [raw])
118
- return raw_without_singleton_methods(raw)
119
- end
120
-
121
- def cache_to_raw_data(cached_data)
122
- return nil if cached_data == NilObject
123
- return false if cached_data == FalseObject
124
- return cached_data
125
- end
126
-
127
- def get_from_cache
128
- ActiveModelCachers.config.store.read(cache_key)
129
- end
130
-
131
- def fetch_from_cache(binding: nil, reflect: nil)
132
- ActiveModelCachers.config.store.fetch(cache_key, expires_in: 30.minutes) do
133
- raw_to_cache_data(get_without_cache(binding, reflect))
134
- end
135
- end
136
-
137
- def clean_ar_cache(models)
138
- return if not models.first.is_a?(::ActiveRecord::Base)
139
- models.each do |model|
140
- model.send(:clear_aggregation_cache) if model.respond_to?(:clear_aggregation_cache, true)
141
- model.send(:clear_association_cache)
142
- end
143
- end
144
-
145
- def raw_without_singleton_methods(raw)
146
- return raw if raw.singleton_methods.empty?
147
- return raw.class.find_by(id: raw.id) if raw.is_a?(::ActiveRecord::Base) # cannot marshal singleton, so load a new record instead.
148
- return raw # not sure what to do with other cases
149
- end
150
- end
151
- end
1
+ # frozen_string_literal: true
2
+ require 'active_model_cachers/nil_object'
3
+ require 'active_model_cachers/false_object'
4
+ require 'active_model_cachers/column_value_cache'
5
+
6
+ module ActiveModelCachers
7
+ class CacheService
8
+ class << self
9
+ attr_accessor :cache_key
10
+ attr_accessor :query_mapping
11
+
12
+ def instance(id)
13
+ hash = (RequestStore.store[self] ||= {})
14
+ return hash[id] ||= new(id)
15
+ end
16
+
17
+ def clean_at(id)
18
+ instance(id).clean_cache
19
+ end
20
+
21
+ @@column_value_cache = ActiveModelCachers::ColumnValueCache.new
22
+ def define_callback_for_cleaning_cache(class_name, column, foreign_key, with_id, on: nil)
23
+ return if @callbacks_defined
24
+ @callbacks_defined = true
25
+
26
+ clean = ->(id){ clean_at(with_id ? id : nil) }
27
+ clean_ids = []
28
+ fire_on = Array(on) if on
29
+
30
+ ActiveRecord::Extension.global_callbacks.instance_exec do
31
+ on_nullify(class_name) do |nullified_column, get_ids|
32
+ get_ids.call.each{|s| clean.call(s) } if nullified_column == column
33
+ end
34
+
35
+ after_touch1(class_name) do
36
+ clean.call(@@column_value_cache.add(self.class, class_name, id, foreign_key, self).call)
37
+ end
38
+
39
+ after_touch2(class_name) do
40
+ @@column_value_cache.clean_cache
41
+ end
42
+
43
+ after_commit1(class_name) do
44
+ next if fire_on and not transaction_include_any_action?(fire_on)
45
+ changed = column ? previous_changes.key?(column) : previous_changes.present?
46
+ if changed || destroyed?
47
+ clean.call(@@column_value_cache.add(self.class, class_name, id, foreign_key, self).call)
48
+ end
49
+ end
50
+
51
+ after_commit2(class_name) do
52
+ @@column_value_cache.clean_cache
53
+ end
54
+
55
+ before_delete1(class_name) do |id, model|
56
+ clean_ids << @@column_value_cache.add(self, class_name, id, foreign_key, model)
57
+ end
58
+
59
+ before_delete2(class_name) do |_, model|
60
+ clean_ids.each{|s| clean.call(s.call) }
61
+ clean_ids = []
62
+ end
63
+
64
+ after_delete(class_name) do
65
+ @@column_value_cache.clean_cache
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # ----------------------------------------------------------------
72
+ # ● instance methods
73
+ # ----------------------------------------------------------------
74
+ def initialize(id)
75
+ @id = id
76
+ end
77
+
78
+ def get(binding: nil, reflect: nil)
79
+ @cached_data ||= fetch_from_cache(binding: binding, reflect: reflect)
80
+ return cache_to_raw_data(@cached_data)
81
+ end
82
+
83
+ def peek(binding: nil, reflect: nil)
84
+ @cached_data ||= get_from_cache
85
+ return cache_to_raw_data(@cached_data)
86
+ end
87
+
88
+ def clean_cache(binding: nil, reflect: nil)
89
+ @cached_data = nil
90
+ Rails.cache.delete(cache_key)
91
+ return nil
92
+ end
93
+
94
+ private
95
+
96
+ def cache_key
97
+ key = self.class.cache_key
98
+ return @id ? "#{key}_#{@id}" : key
99
+ end
100
+
101
+ def get_query(binding, reflect)
102
+ self.class.query_mapping[reflect] || begin
103
+ puts "Warning: cannot find query. possible reflects: #{self.class.query_mapping.keys}, reflect: #{reflect}"
104
+ self.class.query_mapping.values.first
105
+ end
106
+ end
107
+
108
+ def get_without_cache(binding, attr)
109
+ query = get_query(binding, attr)
110
+ return binding ? binding.instance_exec(@id, &query) : query.call(@id) if @id and query.parameters.size == 1
111
+ return binding ? binding.instance_exec(&query) : query.call
112
+ end
113
+
114
+ def raw_to_cache_data(raw)
115
+ return NilObject if raw == nil
116
+ return FalseObject if raw == false
117
+ clean_ar_cache(raw.is_a?(Array) ? raw : [raw])
118
+ return raw_without_singleton_methods(raw)
119
+ end
120
+
121
+ def cache_to_raw_data(cached_data)
122
+ return nil if cached_data == NilObject
123
+ return false if cached_data == FalseObject
124
+ return cached_data
125
+ end
126
+
127
+ def get_from_cache
128
+ ActiveModelCachers.config.store.read(cache_key)
129
+ end
130
+
131
+ def fetch_from_cache(binding: nil, reflect: nil)
132
+ ActiveModelCachers.config.store.fetch(cache_key, expires_in: 30.minutes) do
133
+ raw_to_cache_data(get_without_cache(binding, reflect))
134
+ end
135
+ end
136
+
137
+ def clean_ar_cache(models)
138
+ return if not models.first.is_a?(::ActiveRecord::Base)
139
+ models.each do |model|
140
+ model.send(:clear_aggregation_cache) if model.respond_to?(:clear_aggregation_cache, true)
141
+ model.send(:clear_association_cache)
142
+ end
143
+ end
144
+
145
+ def raw_without_singleton_methods(raw)
146
+ return raw if raw.singleton_methods.empty?
147
+ return raw.class.find_by(id: raw.id) if raw.is_a?(::ActiveRecord::Base) # cannot marshal singleton, so load a new record instead.
148
+ return raw # not sure what to do with other cases
149
+ end
150
+ end
151
+ end
@@ -1,55 +1,55 @@
1
- # frozen_string_literal: true
2
- require 'request_store'
3
- require 'active_model_cachers/cache_service'
4
-
5
- module ActiveModelCachers
6
- class CacheServiceFactory
7
- @key_class_mapping = {}
8
- @cache_key_klass_mapping = {}
9
-
10
- class << self
11
- def has_cacher?(attr)
12
- return (@key_class_mapping[get_cache_key(attr)] != nil)
13
- end
14
-
15
- def create_for_active_model(attr, query)
16
- cache_key = get_cache_key(attr)
17
-
18
- klass = @key_class_mapping[cache_key] ||= ->{
19
- klass = Class.new(CacheService)
20
- klass.cache_key = cache_key
21
- klass.query_mapping = {}
22
- klass.instance_variable_set(:@callbacks_defined, false) # to remove warning: instance variable @callbacks_defined not initialized
23
- next klass
24
- }[]
25
-
26
- klass.query_mapping[attr.reflect] = query
27
- return klass
28
- end
29
-
30
- def set_klass_to_mapping(attr, current_klass)
31
- cache_key = get_cache_key(attr)
32
- changed = clean_klass_cache_if_reloaded!(cache_key, current_klass, attr)
33
- @cache_key_klass_mapping[cache_key] = current_klass
34
- return changed
35
- end
36
-
37
- private
38
-
39
- def get_cache_key(attr)
40
- class_name, column = attr.extract_class_and_column
41
- return "active_model_cachers_#{class_name}_at_#{column}" if column
42
- foreign_key = attr.foreign_key(reverse: true)
43
- return "active_model_cachers_#{class_name}_by_#{foreign_key}" if foreign_key and foreign_key.to_s != 'id'
44
- return "active_model_cachers_#{class_name}"
45
- end
46
-
47
- def clean_klass_cache_if_reloaded!(cache_key, current_klass, attr)
48
- origin_klass, @cache_key_klass_mapping[cache_key] = @cache_key_klass_mapping[cache_key], current_klass
49
- return false if origin_klass == nil or origin_klass == current_klass # when code reloaded in development.
50
- @key_class_mapping[cache_key] = nil
51
- return true
52
- end
53
- end
54
- end
55
- end
1
+ # frozen_string_literal: true
2
+ require 'request_store'
3
+ require 'active_model_cachers/cache_service'
4
+
5
+ module ActiveModelCachers
6
+ class CacheServiceFactory
7
+ @key_class_mapping = {}
8
+ @cache_key_klass_mapping = {}
9
+
10
+ class << self
11
+ def has_cacher?(attr)
12
+ return (@key_class_mapping[get_cache_key(attr)] != nil)
13
+ end
14
+
15
+ def create_for_active_model(attr, query)
16
+ cache_key = get_cache_key(attr)
17
+
18
+ klass = @key_class_mapping[cache_key] ||= ->{
19
+ klass = Class.new(CacheService)
20
+ klass.cache_key = cache_key
21
+ klass.query_mapping = {}
22
+ klass.instance_variable_set(:@callbacks_defined, false) # to remove warning: instance variable @callbacks_defined not initialized
23
+ next klass
24
+ }[]
25
+
26
+ klass.query_mapping[attr.reflect] = query
27
+ return klass
28
+ end
29
+
30
+ def set_klass_to_mapping(attr, current_klass)
31
+ cache_key = get_cache_key(attr)
32
+ changed = clean_klass_cache_if_reloaded!(cache_key, current_klass, attr)
33
+ @cache_key_klass_mapping[cache_key] = current_klass
34
+ return changed
35
+ end
36
+
37
+ private
38
+
39
+ def get_cache_key(attr)
40
+ class_name, column = attr.extract_class_and_column
41
+ return "active_model_cachers_#{class_name}_at_#{column}" if column
42
+ foreign_key = attr.foreign_key(reverse: true)
43
+ return "active_model_cachers_#{class_name}_by_#{foreign_key}" if foreign_key and foreign_key.to_s != 'id'
44
+ return "active_model_cachers_#{class_name}"
45
+ end
46
+
47
+ def clean_klass_cache_if_reloaded!(cache_key, current_klass, attr)
48
+ origin_klass, @cache_key_klass_mapping[cache_key] = @cache_key_klass_mapping[cache_key], current_klass
49
+ return false if origin_klass == nil or origin_klass == current_klass # when code reloaded in development.
50
+ @key_class_mapping[cache_key] = nil
51
+ return true
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,47 +1,47 @@
1
- # frozen_string_literal: true
2
-
3
- class ActiveModelCachers::ColumnValueCache
4
- def initialize
5
- @cache1 = Hash.new{|h, k| h[k] = {} }
6
- @cache2 = Hash.new{|h, k| h[k] = {} }
7
- end
8
-
9
- def add(object, class_name, id, foreign_key, model)
10
- value = (@cache1[class_name][[id, foreign_key]] ||= get_id_from(object, id, foreign_key, model))
11
- return ->{ (value == :not_set ? query_value(object, class_name, id, foreign_key) : value)}
12
- end
13
-
14
- def query_value(object, class_name, id, foreign_key)
15
- cache = @cache2[class_name]
16
- if cache.empty?
17
- no_data_keys = @cache1[class_name].select{|k, v| v == :not_set }.keys
18
- ids = no_data_keys.map(&:first).uniq
19
- columns = ['id', *no_data_keys.map(&:second)].uniq
20
- pluck_columns(object, object.where(id: ids).limit(ids.size), columns).each do |columns_data|
21
- model_id = columns_data.first
22
- columns.each_with_index do |column, index|
23
- cache[[model_id, column]] = columns_data[index]
24
- end
25
- end
26
- end
27
- return cache[[id, foreign_key]]
28
- end
29
-
30
- def clean_cache
31
- @cache1.clear
32
- @cache2.clear
33
- end
34
-
35
- private
36
-
37
- def pluck_columns(_, relation, columns)
38
- relation.pluck(*columns)
39
- end
40
-
41
- def get_id_from(object, id, column, model)
42
- return id if column == 'id'
43
- model ||= object.cacher.peek_by(id: id) if object.has_cacher?
44
- return model.send(column) if model and model.has_attribute?(column)
45
- return :not_set
46
- end
47
- end
1
+ # frozen_string_literal: true
2
+
3
+ class ActiveModelCachers::ColumnValueCache
4
+ def initialize
5
+ @cache1 = Hash.new{|h, k| h[k] = {} }
6
+ @cache2 = Hash.new{|h, k| h[k] = {} }
7
+ end
8
+
9
+ def add(object, class_name, id, foreign_key, model)
10
+ value = (@cache1[class_name][[id, foreign_key]] ||= get_id_from(object, id, foreign_key, model))
11
+ return ->{ (value == :not_set ? query_value(object, class_name, id, foreign_key) : value)}
12
+ end
13
+
14
+ def query_value(object, class_name, id, foreign_key)
15
+ cache = @cache2[class_name]
16
+ if cache.empty?
17
+ no_data_keys = @cache1[class_name].select{|k, v| v == :not_set }.keys
18
+ ids = no_data_keys.map(&:first).uniq
19
+ columns = ['id', *no_data_keys.map(&:second)].uniq
20
+ pluck_columns(object, object.where(id: ids).limit(ids.size), columns).each do |columns_data|
21
+ model_id = columns_data.first
22
+ columns.each_with_index do |column, index|
23
+ cache[[model_id, column]] = columns_data[index]
24
+ end
25
+ end
26
+ end
27
+ return cache[[id, foreign_key]]
28
+ end
29
+
30
+ def clean_cache
31
+ @cache1.clear
32
+ @cache2.clear
33
+ end
34
+
35
+ private
36
+
37
+ def pluck_columns(_, relation, columns)
38
+ relation.pluck(*columns)
39
+ end
40
+
41
+ def get_id_from(object, id, column, model)
42
+ return id if column == 'id'
43
+ model ||= object.cacher.peek_by(id: id) if object.has_cacher?
44
+ return model.send(column) if model and model.has_attribute?(column)
45
+ return :not_set
46
+ end
47
+ end