active_model_cachers 2.1.6 → 2.1.7
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 +5 -5
- data/.gitignore +9 -9
- data/.travis.yml +8 -1
- data/CHANGELOG.md +67 -62
- data/CODE_OF_CONDUCT.md +48 -48
- data/LICENSE.txt +21 -21
- data/README.md +1 -1
- data/Rakefile +10 -10
- data/active_model_cachers.gemspec +7 -0
- data/bin/console +14 -14
- data/bin/setup +8 -8
- data/gemfiles/3.2.gemfile +11 -11
- data/gemfiles/4.2.gemfile +11 -11
- data/gemfiles/5.0.gemfile +11 -11
- data/gemfiles/5.1.gemfile +11 -11
- data/gemfiles/5.2.gemfile +11 -11
- data/gemfiles/6.0.gemfile +11 -0
- data/lib/active_model_cachers.rb +0 -0
- data/lib/active_model_cachers/active_record/attr_model.rb +124 -124
- data/lib/active_model_cachers/active_record/cacher.rb +97 -97
- data/lib/active_model_cachers/active_record/extension.rb +119 -119
- data/lib/active_model_cachers/active_record/global_callbacks.rb +67 -67
- data/lib/active_model_cachers/cache_service.rb +151 -151
- data/lib/active_model_cachers/cache_service_factory.rb +55 -55
- data/lib/active_model_cachers/column_value_cache.rb +47 -47
- data/lib/active_model_cachers/config.rb +6 -6
- data/lib/active_model_cachers/false_object.rb +5 -5
- data/lib/active_model_cachers/hook/associations.rb +43 -43
- data/lib/active_model_cachers/hook/dependencies.rb +38 -38
- data/lib/active_model_cachers/hook/on_model_delete.rb +29 -29
- data/lib/active_model_cachers/nil_object.rb +5 -5
- data/lib/active_model_cachers/patches/patch_rails_3.rb +49 -49
- data/lib/active_model_cachers/patches/uninitialized_attribute.rb +9 -9
- data/lib/active_model_cachers/version.rb +4 -4
- metadata +10 -4
@@ -1,119 +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
|
-
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
|
+
# 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,67 +1,67 @@
|
|
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
|
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
|
@@ -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.
|
140
|
-
model.send(:clear_aggregation_cache)
|
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
|