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,97 +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
- 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
+ # 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,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