super_callbacks 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +439 -394
- data/lib/super_callbacks/version.rb +1 -1
- data/lib/super_callbacks.rb +254 -217
- data/super_callbacks.gemspec +2 -2
- metadata +8 -7
data/lib/super_callbacks.rb
CHANGED
@@ -1,217 +1,254 @@
|
|
1
|
-
require 'super_callbacks/version'
|
2
|
-
|
3
|
-
module SuperCallbacks
|
4
|
-
VALID_OPTION_KEYS = [:if].freeze
|
5
|
-
|
6
|
-
def self.included(base)
|
7
|
-
# prevent re-including
|
8
|
-
return if base.ancestors.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
9
|
-
|
10
|
-
base.singleton_class.send :attr_accessor, *[:before_callbacks, :after_callbacks]
|
11
|
-
base.send :attr_accessor, *[:before_callbacks, :after_callbacks]
|
12
|
-
base.extend ClassMethods
|
13
|
-
base.send :include, InstanceMethods
|
14
|
-
base.extend ClassAndInstanceMethods
|
15
|
-
base.send :include, ClassAndInstanceMethods
|
16
|
-
base.send :prepend, Prepended.new
|
17
|
-
end
|
18
|
-
|
19
|
-
class Prepended < Module
|
20
|
-
end
|
21
|
-
|
22
|
-
module Helpers
|
23
|
-
# (modified) File activesupport/lib/active_support/core_ext/hash/deep_merge.rb, line 18
|
24
|
-
def self.deep_merge_hashes_and_combine_arrays(this_hash, other_hash, &block)
|
25
|
-
self.deep_merge_hashes_and_combine_arrays!(this_hash.dup, other_hash, &block)
|
26
|
-
end
|
27
|
-
|
28
|
-
# (modified) File activesupport/lib/active_support/core_ext/hash/deep_merge.rb, line 23
|
29
|
-
def self.deep_merge_hashes_and_combine_arrays!(this_hash, other_hash, &block)
|
30
|
-
this_hash.merge!(other_hash) do |key, this_val, other_val|
|
31
|
-
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
32
|
-
self.deep_merge_hashes(this_val, other_val, &block)
|
33
|
-
elsif this_val.is_a?(Array) && other_val.is_a?(Array)
|
34
|
-
this_val + other_val
|
35
|
-
elsif block_given?
|
36
|
-
block.call(key, this_val, other_val)
|
37
|
-
else
|
38
|
-
other_val
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
module ClassAndInstanceMethods
|
45
|
-
def before!(method_name, *remaining_args, &callback_proc)
|
46
|
-
raise ArgumentError, "`#{method_name}` is not or not yet defined for #{self}" unless method_defined? method_name
|
47
|
-
before(method_name, *remaining_args, &callback_proc)
|
48
|
-
end
|
49
|
-
|
50
|
-
def after!(method_name, *remaining_args, &callback_proc)
|
51
|
-
raise ArgumentError, "`#{method_name}` is not or not yet defined for #{self}" unless method_defined? method_name
|
52
|
-
after(method_name, *remaining_args, &callback_proc)
|
53
|
-
end
|
54
|
-
|
55
|
-
def before(method_name, callback_method_name = nil, options = {}, &callback_proc)
|
56
|
-
callback_method_name_or_proc = callback_proc || callback_method_name
|
57
|
-
unless [Symbol, String, Proc].any? { |klass| callback_method_name_or_proc.is_a? klass }
|
58
|
-
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `method_name`, but is #{callback_method_name_or_proc.class}"
|
59
|
-
end
|
60
|
-
|
61
|
-
invalid_option_keys = options.keys - VALID_OPTION_KEYS
|
62
|
-
unless invalid_option_keys.empty?
|
63
|
-
raise ArgumentError, "Invalid `options` keys: #{invalid_option_keys}. Valid are only: #{VALID_OPTION_KEYS}"
|
64
|
-
end
|
65
|
-
if options[:if] && !([Symbol, String, Proc].any? { |klass| callback_method_name_or_proc.is_a? klass })
|
66
|
-
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `options[:if]`, but is #{options[:if].class}"
|
67
|
-
end
|
68
|
-
|
69
|
-
self.before_callbacks ||= {}
|
70
|
-
self.before_callbacks[method_name.to_sym] ||= []
|
71
|
-
self.before_callbacks[method_name.to_sym] << [callback_method_name_or_proc, options[:if]]
|
72
|
-
|
73
|
-
_callbacks_prepended_module_instance = callbacks_prepended_module_instance
|
74
|
-
|
75
|
-
# dont redefine, to save cpu cycles
|
76
|
-
unless _callbacks_prepended_module_instance.method_defined? method_name
|
77
|
-
_callbacks_prepended_module_instance.send(:define_method, method_name) do |*args|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
#
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
1
|
+
require 'super_callbacks/version'
|
2
|
+
|
3
|
+
module SuperCallbacks
|
4
|
+
VALID_OPTION_KEYS = [:if].freeze
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
# prevent re-including
|
8
|
+
return if base.ancestors.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
9
|
+
|
10
|
+
base.singleton_class.send :attr_accessor, *[:before_callbacks, :after_callbacks]
|
11
|
+
base.send :attr_accessor, *[:before_callbacks, :after_callbacks]
|
12
|
+
base.extend ClassMethods
|
13
|
+
base.send :include, InstanceMethods
|
14
|
+
base.extend ClassAndInstanceMethods
|
15
|
+
base.send :include, ClassAndInstanceMethods
|
16
|
+
base.send :prepend, Prepended.new
|
17
|
+
end
|
18
|
+
|
19
|
+
class Prepended < Module
|
20
|
+
end
|
21
|
+
|
22
|
+
module Helpers
|
23
|
+
# (modified) File activesupport/lib/active_support/core_ext/hash/deep_merge.rb, line 18
|
24
|
+
def self.deep_merge_hashes_and_combine_arrays(this_hash, other_hash, &block)
|
25
|
+
self.deep_merge_hashes_and_combine_arrays!(this_hash.dup, other_hash, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# (modified) File activesupport/lib/active_support/core_ext/hash/deep_merge.rb, line 23
|
29
|
+
def self.deep_merge_hashes_and_combine_arrays!(this_hash, other_hash, &block)
|
30
|
+
this_hash.merge!(other_hash) do |key, this_val, other_val|
|
31
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
32
|
+
self.deep_merge_hashes(this_val, other_val, &block)
|
33
|
+
elsif this_val.is_a?(Array) && other_val.is_a?(Array)
|
34
|
+
this_val + other_val
|
35
|
+
elsif block_given?
|
36
|
+
block.call(key, this_val, other_val)
|
37
|
+
else
|
38
|
+
other_val
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassAndInstanceMethods
|
45
|
+
def before!(method_name, *remaining_args, &callback_proc)
|
46
|
+
raise ArgumentError, "`#{method_name}` is not or not yet defined for #{self}" unless method_defined? method_name
|
47
|
+
before(method_name, *remaining_args, &callback_proc)
|
48
|
+
end
|
49
|
+
|
50
|
+
def after!(method_name, *remaining_args, &callback_proc)
|
51
|
+
raise ArgumentError, "`#{method_name}` is not or not yet defined for #{self}" unless method_defined? method_name
|
52
|
+
after(method_name, *remaining_args, &callback_proc)
|
53
|
+
end
|
54
|
+
|
55
|
+
def before(method_name, callback_method_name = nil, options = {}, &callback_proc)
|
56
|
+
callback_method_name_or_proc = callback_proc || callback_method_name
|
57
|
+
unless [Symbol, String, Proc].any? { |klass| callback_method_name_or_proc.is_a? klass }
|
58
|
+
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `method_name`, but is #{callback_method_name_or_proc.class}"
|
59
|
+
end
|
60
|
+
|
61
|
+
invalid_option_keys = options.keys - VALID_OPTION_KEYS
|
62
|
+
unless invalid_option_keys.empty?
|
63
|
+
raise ArgumentError, "Invalid `options` keys: #{invalid_option_keys}. Valid are only: #{VALID_OPTION_KEYS}"
|
64
|
+
end
|
65
|
+
if options[:if] && !([Symbol, String, Proc].any? { |klass| callback_method_name_or_proc.is_a? klass })
|
66
|
+
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `options[:if]`, but is #{options[:if].class}"
|
67
|
+
end
|
68
|
+
|
69
|
+
self.before_callbacks ||= {}
|
70
|
+
self.before_callbacks[method_name.to_sym] ||= []
|
71
|
+
self.before_callbacks[method_name.to_sym] << [callback_method_name_or_proc, options[:if]]
|
72
|
+
|
73
|
+
_callbacks_prepended_module_instance = callbacks_prepended_module_instance
|
74
|
+
|
75
|
+
# dont redefine, to save cpu cycles
|
76
|
+
unless _callbacks_prepended_module_instance.method_defined? method_name
|
77
|
+
_callbacks_prepended_module_instance.send(:define_method, method_name) do |*args|
|
78
|
+
begin
|
79
|
+
@instance_variables_before_change = instance_variables.each_with_object({}) do |instance_variable, hash|
|
80
|
+
hash[instance_variable] = instance_variable_get(instance_variable)
|
81
|
+
end
|
82
|
+
|
83
|
+
run_before_callbacks(method_name, *args)
|
84
|
+
super_value = super(*args)
|
85
|
+
run_after_callbacks(method_name, *args)
|
86
|
+
ensure
|
87
|
+
remove_instance_variable(:@instance_variables_before_change)
|
88
|
+
end
|
89
|
+
|
90
|
+
super_value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def after(method_name, callback_method_name = nil, options = {}, &callback_proc)
|
96
|
+
callback_method_name_or_proc = callback_proc || callback_method_name
|
97
|
+
unless [Symbol, String, Proc].include? callback_method_name_or_proc.class
|
98
|
+
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `method_name`, but is #{callback_method_name_or_proc.class}"
|
99
|
+
end
|
100
|
+
|
101
|
+
invalid_option_keys = options.keys - VALID_OPTION_KEYS
|
102
|
+
unless invalid_option_keys.empty?
|
103
|
+
raise ArgumentError, "Invalid `options` keys: #{invalid_option_keys}. Valid are only: #{VALID_OPTION_KEYS}"
|
104
|
+
end
|
105
|
+
if options[:if] && ![Symbol, String, Proc].include?(options[:if].class)
|
106
|
+
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `options[:if]`, but is #{options[:if].class}"
|
107
|
+
end
|
108
|
+
|
109
|
+
self.after_callbacks ||= {}
|
110
|
+
self.after_callbacks[method_name.to_sym] ||= []
|
111
|
+
self.after_callbacks[method_name.to_sym] << [callback_method_name_or_proc, options[:if]]
|
112
|
+
|
113
|
+
_callbacks_prepended_module_instance = callbacks_prepended_module_instance
|
114
|
+
|
115
|
+
# dont redefine, to save cpu cycles
|
116
|
+
unless _callbacks_prepended_module_instance.method_defined? method_name
|
117
|
+
_callbacks_prepended_module_instance.send(:define_method, method_name) do |*args|
|
118
|
+
begin
|
119
|
+
@instance_variables_before_change = instance_variables.each_with_object({}) do |instance_variable, hash|
|
120
|
+
hash[instance_variable] = instance_variable_get(instance_variable)
|
121
|
+
end
|
122
|
+
|
123
|
+
run_before_callbacks(method_name, *args)
|
124
|
+
super_value = super(*args)
|
125
|
+
run_after_callbacks(method_name, *args)
|
126
|
+
ensure
|
127
|
+
remove_instance_variable(:@instance_variables_before_change)
|
128
|
+
end
|
129
|
+
|
130
|
+
super_value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def instance_variables_before_change
|
136
|
+
@instance_variables_before_change
|
137
|
+
end
|
138
|
+
|
139
|
+
def instance_variable_before_change(instance_variable)
|
140
|
+
raise ArgumentError, "#{instance_variable} should be a string that starts with `@`" unless instance_variable.to_s.start_with? '@'
|
141
|
+
raise 'You cannot call this method outside the SuperCallback cycle' if instance_variables_before_change.nil?
|
142
|
+
instance_variables_before_change[instance_variable]
|
143
|
+
end
|
144
|
+
|
145
|
+
def instance_variable_changed?(instance_variable)
|
146
|
+
raise ArgumentError, "#{instance_variable} should be a string that starts with `@`" unless instance_variable.to_s.start_with? '@'
|
147
|
+
raise 'You cannot call this method outside the SuperCallback cycle' if instance_variables_before_change.nil?
|
148
|
+
|
149
|
+
before_change_value = instance_variable_before_change(instance_variable)
|
150
|
+
current_value = instance_variable_get(instance_variable)
|
151
|
+
before_change_value != current_value
|
152
|
+
end
|
153
|
+
|
154
|
+
# TODO
|
155
|
+
# def around
|
156
|
+
# end
|
157
|
+
end
|
158
|
+
|
159
|
+
module ClassMethods
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def callbacks_prepended_module_instance
|
164
|
+
ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
module InstanceMethods
|
169
|
+
# TODO: optimize by instead of dynamically getting all_ancestral_after_callbacks on runtime
|
170
|
+
# set them immediately when `include` is called on Base class
|
171
|
+
def run_before_callbacks(method_name, *args)
|
172
|
+
all_ancestral_before_callbacks = self.class.ancestors.reverse.each_with_object({}) do |ancestor, hash|
|
173
|
+
SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays!(
|
174
|
+
hash,
|
175
|
+
ancestor.instance_variable_get(:@before_callbacks) || {}
|
176
|
+
)
|
177
|
+
end
|
178
|
+
|
179
|
+
singleton_class_before_callbacks = instance_variable_get(:@before_callbacks) || {}
|
180
|
+
|
181
|
+
all_before_callbacks = SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays(
|
182
|
+
all_ancestral_before_callbacks,
|
183
|
+
singleton_class_before_callbacks
|
184
|
+
)
|
185
|
+
|
186
|
+
all_before_callbacks_on_method = all_before_callbacks[method_name] || []
|
187
|
+
|
188
|
+
all_before_callbacks_on_method.each do |before_callback, options_if|
|
189
|
+
is_condition_truthy = true
|
190
|
+
|
191
|
+
if options_if
|
192
|
+
is_condition_truthy = instance_exec *args, &options_if
|
193
|
+
end
|
194
|
+
|
195
|
+
if is_condition_truthy
|
196
|
+
if before_callback.is_a? Proc
|
197
|
+
instance_exec *args, &before_callback
|
198
|
+
else
|
199
|
+
send before_callback
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# TODO: optimize by instead of dynamically getting all_ancestral_after_callbacks on runtime
|
206
|
+
# set them immediately when `include` is called on Base class
|
207
|
+
def run_after_callbacks(method_name, *args)
|
208
|
+
all_ancestral_after_callbacks = self.class.ancestors.reverse.each_with_object({}) do |ancestor, hash|
|
209
|
+
SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays!(
|
210
|
+
hash,
|
211
|
+
ancestor.instance_variable_get(:@after_callbacks) || {}
|
212
|
+
)
|
213
|
+
end
|
214
|
+
|
215
|
+
singleton_class_after_callbacks = instance_variable_get(:@after_callbacks) || {}
|
216
|
+
|
217
|
+
all_after_callbacks = SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays(
|
218
|
+
all_ancestral_after_callbacks,
|
219
|
+
singleton_class_after_callbacks
|
220
|
+
)
|
221
|
+
|
222
|
+
all_after_callbacks_on_method = all_after_callbacks[method_name] || []
|
223
|
+
|
224
|
+
all_after_callbacks_on_method.each do |after_callback, options_if|
|
225
|
+
is_condition_truthy = true
|
226
|
+
|
227
|
+
if options_if
|
228
|
+
is_condition_truthy = instance_exec *args, &options_if
|
229
|
+
end
|
230
|
+
|
231
|
+
if is_condition_truthy
|
232
|
+
if after_callback.is_a? Proc
|
233
|
+
instance_exec *args, &after_callback
|
234
|
+
else
|
235
|
+
send after_callback
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def callbacks_prepended_module_instance
|
244
|
+
_callbacks_prepended_module_instance = self.singleton_class.ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
245
|
+
|
246
|
+
if _callbacks_prepended_module_instance.nil?
|
247
|
+
self.singleton_class.prepend SuperCallbacks::Prepended
|
248
|
+
_callbacks_prepended_module_instance = self.singleton_class.ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
249
|
+
end
|
250
|
+
|
251
|
+
_callbacks_prepended_module_instance
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
data/super_callbacks.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ['Jules Roman B. Polidario']
|
9
9
|
spec.email = ['jules@topfloor.ie']
|
10
10
|
|
11
|
-
spec.summary = 'Allows `before` and `after` callbacks to any Class. Supports
|
12
|
-
spec.description = 'Allows `before` and `after` callbacks to any Class. Supports
|
11
|
+
spec.summary = 'Allows `before` and `after` callbacks to any Class. Supports dirty checking of instance variables changes, class and instance level callbacks, conditional callbacks, and inherited callbacks.'
|
12
|
+
spec.description = 'Allows `before` and `after` callbacks to any Class. Supports dirty checking of instance variables changes, class and instance level callbacks, conditional callbacks, and inherited callbacks. Focuses on performance and flexibility as intended primarily for game development, and event-driven apps.'
|
13
13
|
spec.homepage = 'https://github.com/jrpolidario/super_callbacks'
|
14
14
|
spec.license = 'MIT'
|
15
15
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: super_callbacks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jules Roman B. Polidario
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '9.0'
|
69
|
-
description: Allows `before` and `after` callbacks to any Class. Supports
|
70
|
-
and instance level callbacks, conditional callbacks,
|
71
|
-
on performance and flexibility as intended primarily
|
72
|
-
apps.
|
69
|
+
description: Allows `before` and `after` callbacks to any Class. Supports dirty checking
|
70
|
+
of instance variables changes, class and instance level callbacks, conditional callbacks,
|
71
|
+
and inherited callbacks. Focuses on performance and flexibility as intended primarily
|
72
|
+
for game development, and event-driven apps.
|
73
73
|
email:
|
74
74
|
- jules@topfloor.ie
|
75
75
|
executables: []
|
@@ -112,6 +112,7 @@ rubyforge_project:
|
|
112
112
|
rubygems_version: 2.4.5.5
|
113
113
|
signing_key:
|
114
114
|
specification_version: 4
|
115
|
-
summary: Allows `before` and `after` callbacks to any Class. Supports
|
116
|
-
instance level callbacks, conditional callbacks,
|
115
|
+
summary: Allows `before` and `after` callbacks to any Class. Supports dirty checking
|
116
|
+
of instance variables changes, class and instance level callbacks, conditional callbacks,
|
117
|
+
and inherited callbacks.
|
117
118
|
test_files: []
|