active_model_cachers 2.1.3 → 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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +59 -0
  3. data/.gitignore +9 -9
  4. data/CHANGELOG.md +71 -50
  5. data/CODE_OF_CONDUCT.md +48 -48
  6. data/LICENSE.txt +21 -21
  7. data/README.md +399 -353
  8. data/Rakefile +10 -10
  9. data/active_model_cachers.gemspec +45 -38
  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 -0
  18. data/lib/active_model_cachers.rb +11 -0
  19. data/lib/active_model_cachers/active_record/attr_model.rb +124 -103
  20. data/lib/active_model_cachers/active_record/cacher.rb +97 -96
  21. data/lib/active_model_cachers/active_record/extension.rb +119 -106
  22. data/lib/active_model_cachers/active_record/global_callbacks.rb +67 -59
  23. data/lib/active_model_cachers/cache_service.rb +151 -134
  24. data/lib/active_model_cachers/cache_service_factory.rb +55 -55
  25. data/lib/active_model_cachers/column_value_cache.rb +47 -46
  26. data/lib/active_model_cachers/config.rb +6 -6
  27. data/lib/active_model_cachers/false_object.rb +5 -5
  28. data/lib/active_model_cachers/hook/associations.rb +43 -43
  29. data/lib/active_model_cachers/hook/dependencies.rb +38 -38
  30. data/lib/active_model_cachers/hook/on_model_delete.rb +29 -29
  31. data/lib/active_model_cachers/nil_object.rb +5 -5
  32. data/lib/active_model_cachers/{active_record → patches}/patch_rails_3.rb +49 -49
  33. data/lib/active_model_cachers/patches/uninitialized_attribute.rb +9 -0
  34. data/lib/active_model_cachers/version.rb +4 -4
  35. metadata +31 -13
  36. data/.travis.yml +0 -26
@@ -1,96 +1,97 @@
1
- # frozen_string_literal: true
2
- module ActiveModelCachers
3
- module ActiveRecord
4
- class Cacher
5
- @defined_map = {}
6
-
7
- class << self
8
- def get_cacher_klass(klass)
9
- @defined_map[klass] ||= create_cacher_klass_at(klass)
10
- end
11
-
12
- def define_cacher_method(attr, primary_key, service_klasses)
13
- cacher_klass = get_cacher_klass(attr.klass)
14
- method = attr.column
15
- return cacher_klass.define_find_by(attr, primary_key, service_klasses) if method == nil
16
- cacher_klass.send(:define_methods, method, {
17
- method => ->{ exec_by(attr, primary_key, service_klasses, :get) },
18
- "peek_#{method}" => ->{ exec_by(attr, primary_key, service_klasses, :peek) },
19
- "clean_#{method}" => ->{ exec_by(attr, primary_key, service_klasses, :clean_cache) },
20
- })
21
- end
22
-
23
- def define_find_by(attr, primary_key, service_klasses)
24
- if @find_by_mapping == nil
25
- @find_by_mapping = {}
26
- define_methods(:find_by, {
27
- :find_by => ->(args){ exec_find_by(args, :get) },
28
- :peek_by => ->(args){ exec_find_by(args, :peek) },
29
- :clean_by => ->(args){ exec_find_by(args, :clean_cache) },
30
- })
31
- end
32
- @find_by_mapping[primary_key] = [attr, service_klasses]
33
- end
34
-
35
- private
36
-
37
- def define_methods(attribute, methods_mapping)
38
- if attributes.include?(attribute)
39
- methods_mapping.keys.each{|s| undef_method(s) }
40
- else
41
- attributes << attribute
42
- end
43
- methods_mapping.each{|method, block| define_method(method, &block) }
44
- end
45
-
46
- def get_data_from_find_by_mapping(primary_key)
47
- return if @find_by_mapping == nil
48
- return @find_by_mapping[primary_key]
49
- end
50
-
51
- def create_cacher_klass_at(target)
52
- cacher_klass = Class.new(self)
53
- cacher_klass.instance_variable_set(:@find_by_mapping, nil) # to remove warning: instance variable @find_by_mapping not initialized
54
- cacher_klass.define_singleton_method(:attributes){ @attributes ||= [] }
55
- cacher_klass.send(:define_method, 'peek'){|column| send("peek_#{column}") }
56
- cacher_klass.send(:define_method, 'clean'){|column| send("clean_#{column}") }
57
-
58
- target.define_singleton_method(:cacher_at){|id| cacher_klass.new(id: id) }
59
- target.define_singleton_method(:cacher){ cacher_klass.new }
60
- target.send(:define_method, :cacher){ cacher_klass.new(model: self) }
61
- return cacher_klass
62
- end
63
- end
64
-
65
- def initialize(id: nil, model: nil)
66
- @id = id
67
- @model = model
68
- end
69
-
70
- private
71
-
72
- def exec_find_by(args, method) # e.g. args = {course_id: xx}
73
- primary_key = args.keys.sort.first # Support only one key now.
74
- attr, service_klasses = self.class.send(:get_data_from_find_by_mapping, primary_key)
75
- return if service_klasses == nil
76
- return exec_by(attr, primary_key, service_klasses, method, data: args[primary_key])
77
- end
78
-
79
- def exec_by(attr, primary_key, service_klasses, method, data: nil)
80
- bindings = [@model]
81
- if @model and attr.association?
82
- if attr.belongs_to? and method != :clean_cache # no need to load binding when just cleaning cache
83
- association = @model.association(attr.column)
84
- bindings << association.load_target if association.loaded?
85
- end
86
- end
87
- data ||= (@model ? @model.send(primary_key) : nil) || @id
88
- service_klasses.each_with_index do |service_klass, index|
89
- data = service_klass.instance(data).send(method, binding: bindings[index])
90
- return if data == nil
91
- end
92
- return data
93
- end
94
- end
95
- end
96
- end
1
+ # frozen_string_literal: true
2
+ module ActiveModelCachers
3
+ module ActiveRecord
4
+ class Cacher
5
+ @defined_map = {}
6
+
7
+ class << self
8
+ def get_cacher_klass(klass)
9
+ @defined_map[klass] ||= create_cacher_klass_at(klass)
10
+ end
11
+
12
+ def define_cacher_method(attr, primary_key, service_klasses)
13
+ cacher_klass = get_cacher_klass(attr.klass)
14
+ method = attr.column
15
+ return cacher_klass.define_find_by(attr, primary_key, service_klasses) if method == nil
16
+ cacher_klass.send(:define_methods, method, {
17
+ method => ->{ exec_by(attr, primary_key, service_klasses, :get) },
18
+ "peek_#{method}" => ->{ exec_by(attr, primary_key, service_klasses, :peek) },
19
+ "clean_#{method}" => ->{ exec_by(attr, primary_key, service_klasses, :clean_cache) },
20
+ })
21
+ end
22
+
23
+ def define_find_by(attr, primary_key, service_klasses)
24
+ if @find_by_mapping == nil
25
+ @find_by_mapping = {}
26
+ define_methods(:find_by, {
27
+ :find_by => ->(args){ exec_find_by(args, :get) },
28
+ :peek_by => ->(args){ exec_find_by(args, :peek) },
29
+ :clean_by => ->(args){ exec_find_by(args, :clean_cache) },
30
+ })
31
+ end
32
+ @find_by_mapping[primary_key] = [attr, service_klasses]
33
+ end
34
+
35
+ private
36
+
37
+ def define_methods(attribute, methods_mapping)
38
+ if attributes.include?(attribute)
39
+ methods_mapping.keys.each{|s| undef_method(s) }
40
+ else
41
+ attributes << attribute
42
+ end
43
+ methods_mapping.each{|method, block| define_method(method, &block) }
44
+ end
45
+
46
+ def get_data_from_find_by_mapping(primary_key)
47
+ return if @find_by_mapping == nil
48
+ return @find_by_mapping[primary_key]
49
+ end
50
+
51
+ def create_cacher_klass_at(target)
52
+ cacher_klass = Class.new(self)
53
+ cacher_klass.instance_variable_set(:@find_by_mapping, nil) # to remove warning: instance variable @find_by_mapping not initialized
54
+ cacher_klass.define_singleton_method(:attributes){ @attributes ||= [] }
55
+ cacher_klass.send(:define_method, 'peek'){|column| send("peek_#{column}") }
56
+ cacher_klass.send(:define_method, 'clean'){|column| send("clean_#{column}") }
57
+
58
+ target.define_singleton_method(:cacher_at){|id| cacher_klass.new(id: id) }
59
+ target.define_singleton_method(:cacher){ cacher_klass.new }
60
+ target.send(:define_method, :cacher){ cacher_klass.new(model: self) }
61
+ return cacher_klass
62
+ end
63
+ end
64
+
65
+ def initialize(id: nil, model: nil)
66
+ @id = id
67
+ @model = model
68
+ end
69
+
70
+ private
71
+
72
+ def exec_find_by(args, method) # e.g. args = {course_id: xx}
73
+ primary_key = args.keys.sort.first # Support only one key now.
74
+ attr, service_klasses = self.class.send(:get_data_from_find_by_mapping, primary_key)
75
+ return if service_klasses == nil
76
+ return exec_by(attr, primary_key, service_klasses, method, data: args[primary_key])
77
+ end
78
+
79
+ def exec_by(attr, primary_key, service_klasses, method, data: nil)
80
+ bindings = [@model]
81
+ reflects = (attr.belongs_to? ? [] : [attr.reflect])
82
+ if @model and attr.association?
83
+ if attr.belongs_to? and method != :clean_cache # no need to load binding when just cleaning cache
84
+ association = @model.association(attr.column)
85
+ bindings << association.load_target if association.loaded?
86
+ end
87
+ end
88
+ data ||= (@model ? @model.send(primary_key) : nil) || @id
89
+ service_klasses.each_with_index do |service_klass, index|
90
+ data = service_klass.instance(data).send(method, binding: bindings[index], reflect: reflects[index])
91
+ return if data == nil
92
+ end
93
+ return data
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,106 +1,119 @@
1
- # frozen_string_literal: true
2
- require 'active_model_cachers/active_record/global_callbacks'
3
- require 'active_model_cachers/active_record/attr_model'
4
- require 'active_model_cachers/active_record/cacher'
5
- require 'active_model_cachers/hook/dependencies'
6
- require 'active_model_cachers/hook/associations'
7
- require 'active_model_cachers/hook/on_model_delete'
8
-
9
- module ActiveModelCachers
10
- module ActiveRecord
11
- module Extension
12
- def cache_self(by: :id)
13
- cache_at(nil, expire_by: self.name, primary_key: by, foreign_key: by)
14
- end
15
-
16
- def cache_at(column, query = nil, expire_by: nil, on: nil, foreign_key: nil, primary_key: nil)
17
- attr = AttrModel.new(self, column, foreign_key: foreign_key, primary_key: primary_key)
18
- return cache_belongs_to(attr) if attr.belongs_to?
19
-
20
- loaded = false
21
- class_name, *infos = get_expire_infos(attr, expire_by, foreign_key)
22
- set_klass_to_mapping(attr, class_name) do
23
- next if !loaded
24
- cache_at(column, query, expire_by: expire_by, on: on, foreign_key: foreign_key, primary_key: primary_key)
25
- end
26
- loaded = true
27
-
28
- query ||= ->(id){ attr.query_model(self, id) }
29
- service_klass = CacheServiceFactory.create_for_active_model(attr, query)
30
- Cacher.define_cacher_method(attr, attr.primary_key || :id, [service_klass])
31
-
32
- if class_name
33
- with_id = (expire_by.is_a?(Symbol) || query.parameters.size == 1)
34
- service_klass.define_callback_for_cleaning_cache(class_name, *infos, with_id, on: on)
35
- end
36
-
37
- return service_klass
38
- end
39
-
40
- def has_cacher?(column = nil)
41
- attr = AttrModel.new(self, column)
42
- return CacheServiceFactory.has_cacher?(attr)
43
- end
44
-
45
- private
46
-
47
- def set_klass_to_mapping(attr, class_name)
48
- ActiveSupport::Dependencies.onload(class_name || self.to_s) do
49
- yield if CacheServiceFactory.set_klass_to_mapping(attr, self)
50
- end
51
- end
52
-
53
- def get_expire_infos(attr, expire_by, foreign_key)
54
- if expire_by.is_a?(Symbol)
55
- expire_attr = get_association_attr(expire_by)
56
- expire_by = get_expire_by_from(expire_attr)
57
- else
58
- expire_attr = attr
59
- expire_by ||= get_expire_by_from(expire_attr)
60
- end
61
- return if expire_by == nil
62
-
63
- class_name, column = expire_by.split('#', 2)
64
- foreign_key ||= expire_attr.foreign_key(reverse: true) || 'id'
65
-
66
- return class_name, column, foreign_key.to_s
67
- end
68
-
69
- def get_association_attr(column)
70
- attr = AttrModel.new(self, column)
71
- raise "#{column} is not an association" if not attr.association?
72
- return attr
73
- end
74
-
75
- def get_expire_by_from(attr)
76
- return attr.class_name if attr.association?
77
- return "#{self}##{attr.column}" if column_names.include?(attr.column.to_s)
78
- end
79
-
80
- def cache_belongs_to(attr)
81
- service_klasses = [cache_at(attr.foreign_key)]
82
- Cacher.define_cacher_method(attr, attr.primary_key, service_klasses)
83
- ActiveSupport::Dependencies.onload(attr.class_name) do
84
- service_klasses << cache_self
85
- end
86
- end
87
-
88
- @global_callbacks = GlobalCallbacks.new
89
- def self.global_callbacks
90
- @global_callbacks
91
- end
92
-
93
- def self.extended(base)
94
- global_callbacks = @global_callbacks
95
- base.instance_exec do
96
- after_commit ->{ global_callbacks.after_commit.exec(self, self.class) }
97
- after_touch ->{ global_callbacks.after_touch.exec(self, self.class) }
98
- end
99
- end
100
- end
101
- end
102
- end
103
-
104
- if Gem::Version.new(ActiveRecord::VERSION::STRING) < Gem::Version.new('4')
105
- require 'active_model_cachers/active_record/patch_rails_3'
106
- end
1
+ # frozen_string_literal: true
2
+ require 'active_model_cachers/active_record/global_callbacks'
3
+ require 'active_model_cachers/active_record/attr_model'
4
+ require 'active_model_cachers/active_record/cacher'
5
+ require 'active_model_cachers/hook/dependencies'
6
+ require 'active_model_cachers/hook/associations'
7
+ require 'active_model_cachers/hook/on_model_delete'
8
+
9
+ module ActiveModelCachers
10
+ module ActiveRecord
11
+ module Extension
12
+ def cache_self(by: :id)
13
+ cache_at(nil, expire_by: self.name, primary_key: by, foreign_key: by)
14
+ end
15
+
16
+ def cache_at(column, query = nil, expire_by: nil, on: nil, foreign_key: nil, primary_key: nil)
17
+ attr = AttrModel.new(self, column, foreign_key: foreign_key, primary_key: primary_key)
18
+ return cache_belongs_to(attr) if attr.belongs_to?
19
+
20
+ loaded = false
21
+ class_name, *infos = get_expire_infos(attr, expire_by, foreign_key)
22
+ set_klass_to_mapping(attr, class_name) do
23
+ next if !loaded
24
+ cache_at(column, query, expire_by: expire_by, on: on, foreign_key: foreign_key, primary_key: primary_key)
25
+ end
26
+ loaded = true
27
+
28
+ query ||= ->(id){ attr.query_model(self, id) }
29
+ service_klass = CacheServiceFactory.create_for_active_model(attr, query)
30
+ Cacher.define_cacher_method(attr, attr.primary_key || :id, [service_klass])
31
+
32
+ if class_name
33
+ with_id = (expire_by.is_a?(Symbol) || query.parameters.size == 1)
34
+ service_klass.define_callback_for_cleaning_cache(class_name, *infos, with_id, on: on)
35
+ end
36
+
37
+ return service_klass
38
+ end
39
+
40
+ def has_cacher?(column = nil)
41
+ attr = AttrModel.new(self, column)
42
+ return CacheServiceFactory.has_cacher?(attr)
43
+ end
44
+
45
+ private
46
+
47
+ def set_klass_to_mapping(attr, class_name)
48
+ ActiveSupport::Dependencies.onload(class_name || self.to_s) do
49
+ yield if CacheServiceFactory.set_klass_to_mapping(attr, self)
50
+ end
51
+ end
52
+
53
+ def get_expire_infos(attr, expire_by, foreign_key)
54
+ if expire_by.is_a?(Symbol)
55
+ expire_attr = get_association_attr(expire_by)
56
+ if expire_attr.join_table_class_name
57
+ expire_attr.klass.send(:"after_add_for_#{expire_by}") << gen_has_many_through_callback(attr.column)
58
+ expire_attr.klass.send(:"after_remove_for_#{expire_by}") << gen_has_many_through_callback(attr.column)
59
+ expire_by = expire_attr.join_table_class_name
60
+ else
61
+ expire_by = get_expire_by_from(expire_attr)
62
+ end
63
+ else
64
+ expire_attr = attr
65
+ expire_by ||= get_expire_by_from(expire_attr)
66
+ end
67
+ return if expire_by == nil
68
+
69
+ class_name, column = expire_by.split('#', 2)
70
+ foreign_key ||= expire_attr.foreign_key(reverse: true) || 'id'
71
+
72
+ return class_name, column, foreign_key.to_s
73
+ end
74
+
75
+ NO_MACRO_FOR_AFTER_ADD = Gem::Version.new(::ActiveRecord::VERSION::STRING) < Gem::Version.new('4')
76
+ def gen_has_many_through_callback(column)
77
+ return ->(this, _that){ this.cacher.clean(column) } if NO_MACRO_FOR_AFTER_ADD
78
+ return ->(_, this, _that){ this.cacher.clean(column) }
79
+ end
80
+
81
+ def get_association_attr(column)
82
+ attr = AttrModel.new(self, column)
83
+ raise "#{column} is not an association" if not attr.association?
84
+ return attr
85
+ end
86
+
87
+ def get_expire_by_from(attr)
88
+ return attr.class_name if attr.association?
89
+ return "#{self}##{attr.column}" if column_names.include?(attr.column.to_s)
90
+ end
91
+
92
+ def cache_belongs_to(attr)
93
+ service_klasses = [cache_at(attr.foreign_key)]
94
+ Cacher.define_cacher_method(attr, attr.primary_key, service_klasses)
95
+ ActiveSupport::Dependencies.onload(attr.class_name) do
96
+ service_klasses << cache_self
97
+ end
98
+ end
99
+
100
+ @global_callbacks = nil
101
+ def self.global_callbacks
102
+ if @global_callbacks == nil
103
+ global_callbacks = @global_callbacks = GlobalCallbacks.new
104
+ ::ActiveRecord::Base.instance_exec do
105
+ after_commit ->{
106
+ global_callbacks.after_commit1.exec(self, self.class)
107
+ global_callbacks.after_commit2.exec(self, self.class)
108
+ }
109
+ after_touch ->{
110
+ global_callbacks.after_touch1.exec(self, self.class)
111
+ global_callbacks.after_touch2.exec(self, self.class)
112
+ }
113
+ end
114
+ end
115
+ return @global_callbacks
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,59 +1,67 @@
1
- module ActiveModelCachers
2
- module ActiveRecord
3
- class GlobalCallbacks
4
- def initialize
5
- @type_callbacks = {}
6
- end
7
-
8
- def pre_before_delete(class_name = nil, &block)
9
- define_callbacks(:pre_before_delete, class_name, &block)
10
- end
11
-
12
- def before_delete(class_name = nil, &block)
13
- define_callbacks(:before_delete, class_name, &block)
14
- end
15
-
16
- def after_delete(class_name = nil, &block)
17
- define_callbacks(:after_delete, class_name, &block)
18
- end
19
-
20
- def on_nullify(class_name = nil, &block)
21
- define_callbacks(:on_nullify, class_name, &block)
22
- end
23
-
24
- def after_commit(class_name = nil, &block)
25
- define_callbacks(:after_commit, class_name, &block)
26
- end
27
-
28
- def after_touch(class_name = nil, &block)
29
- define_callbacks(:after_touch, class_name, &block)
30
- end
31
-
32
- private
33
-
34
- def define_callbacks(type, class_name, &block)
35
- (@type_callbacks[type] ||= ClassCallbacks.new).tap do |s|
36
- s.add_callback(class_name, &block) if class_name
37
- end
38
- end
39
- end
40
-
41
- class ClassCallbacks
42
- def initialize
43
- @class_callbacks = Hash.new{|h, k| h[k] = [] }
44
- end
45
-
46
- def callbacks_at(class_name)
47
- @class_callbacks[class_name]
48
- end
49
-
50
- def add_callback(class_name, &block)
51
- callbacks_at(class_name) << block
52
- end
53
-
54
- def exec(scope, klass, *args)
55
- callbacks_at(klass.name).each{|s| scope.instance_exec(*args, &s) }
56
- end
57
- end
58
- end
59
- end
1
+ module ActiveModelCachers
2
+ module ActiveRecord
3
+ class GlobalCallbacks
4
+ def initialize
5
+ @type_callbacks = {}
6
+ end
7
+
8
+ def before_delete1(class_name = nil, &block)
9
+ define_callbacks(:before_delete1, class_name, &block)
10
+ end
11
+
12
+ def before_delete2(class_name = nil, &block)
13
+ define_callbacks(:before_delete2, class_name, &block)
14
+ end
15
+
16
+ def after_delete(class_name = nil, &block)
17
+ define_callbacks(:after_delete, class_name, &block)
18
+ end
19
+
20
+ def on_nullify(class_name = nil, &block)
21
+ define_callbacks(:on_nullify, class_name, &block)
22
+ end
23
+
24
+ def after_commit1(class_name = nil, &block)
25
+ define_callbacks(:after_commit1, class_name, &block)
26
+ end
27
+
28
+ def after_commit2(class_name = nil, &block)
29
+ define_callbacks(:after_commit2, class_name, &block)
30
+ end
31
+
32
+ def after_touch1(class_name = nil, &block)
33
+ define_callbacks(:after_touch1, class_name, &block)
34
+ end
35
+
36
+ def after_touch2(class_name = nil, &block)
37
+ define_callbacks(:after_touch2, class_name, &block)
38
+ end
39
+
40
+ private
41
+
42
+ def define_callbacks(type, class_name, &block)
43
+ (@type_callbacks[type] ||= ClassCallbacks.new).tap do |s|
44
+ s.add_callback(class_name, &block) if class_name
45
+ end
46
+ end
47
+ end
48
+
49
+ class ClassCallbacks
50
+ def initialize
51
+ @class_callbacks = Hash.new{|h, k| h[k] = [] }
52
+ end
53
+
54
+ def callbacks_at(class_name)
55
+ @class_callbacks[class_name]
56
+ end
57
+
58
+ def add_callback(class_name, &block)
59
+ callbacks_at(class_name) << block
60
+ end
61
+
62
+ def exec(scope, klass, *args)
63
+ callbacks_at(klass.name).each{|s| scope.instance_exec(*args, &s) }
64
+ end
65
+ end
66
+ end
67
+ end