super_callbacks 1.1.2 → 1.2.0
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 +4 -4
- data/.ruby-version +1 -1
- data/README.md +5 -1
- data/lib/super_callbacks.rb +34 -225
- data/lib/super_callbacks/class_and_instance_methods.rb +146 -0
- data/lib/super_callbacks/class_methods.rb +10 -0
- data/lib/super_callbacks/helpers.rb +23 -0
- data/lib/super_callbacks/instance_methods.rb +89 -0
- data/lib/super_callbacks/prepended.rb +9 -0
- data/lib/super_callbacks/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1421ece86724a74cf1d418b8d78149445cf42147
|
4
|
+
data.tar.gz: 3fbe860e516f3b0f68a4025b2e8200218a2c7a56
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 336f098ce35d7485319a625558df3e224eaac0e4fe0afc1b5eec0c08c7c1d290ff8ed87e0e7d518609ca0e6ef17bce86196011ca32b2d773d66eb3c0096c50b9
|
7
|
+
data.tar.gz: bdc621d66c9712b031b63a313e879fbe112ef448407ff8790f77137b03dc8d43432f8d5040d8f4617f895678eb6339ed266498c95fbf612c252df01317be6928
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.2.10
|
1
|
+
2.2.10
|
data/README.md
CHANGED
@@ -23,7 +23,7 @@
|
|
23
23
|
Add this line to your application's Gemfile:
|
24
24
|
|
25
25
|
```ruby
|
26
|
-
gem 'super_callbacks', '~> 1.
|
26
|
+
gem 'super_callbacks', '~> 1.2'
|
27
27
|
```
|
28
28
|
|
29
29
|
And then execute:
|
@@ -425,6 +425,10 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
425
425
|
|
426
426
|
## Changelog
|
427
427
|
|
428
|
+
* 1.2.0 (2019-08-15)
|
429
|
+
* [Fixed / Supported Nested "Dirty" Changes on nested callbacks for idempotency (Thread-safe)](https://github.com/jrpolidario/super_callbacks/blob/4e9284e1c6150c6d5da536766d51b4e635bb819d/spec/super_callbacks_spec.rb#L487)
|
430
|
+
* Housecleaning: segregated super_callbacks.rb into multiple files.
|
431
|
+
* Now puts "overriden" warning messages when `SuperCallbacks` is included to a class which already has methods conflicting `SuperCallback`'s DSL-methods.
|
428
432
|
* 1.1.2 (2019-08-14)
|
429
433
|
* [Supported "dirty" checking of instance variable changes](#example-10-dirty-checking-of-instance-variables-changes)
|
430
434
|
* 1.0.3 (2019-08-12)
|
data/lib/super_callbacks.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
1
|
require 'super_callbacks/version'
|
2
|
+
require 'super_callbacks/helpers'
|
3
|
+
require 'super_callbacks/prepended'
|
4
|
+
require 'super_callbacks/class_methods'
|
5
|
+
require 'super_callbacks/instance_methods'
|
6
|
+
require 'super_callbacks/class_and_instance_methods'
|
2
7
|
|
3
8
|
module SuperCallbacks
|
4
9
|
VALID_OPTION_KEYS = [:if].freeze
|
@@ -7,6 +12,8 @@ module SuperCallbacks
|
|
7
12
|
# prevent re-including
|
8
13
|
return if base.ancestors.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
9
14
|
|
15
|
+
puts_warning_messages_when_methods_already_defined(base)
|
16
|
+
|
10
17
|
base.singleton_class.send :attr_accessor, *[:before_callbacks, :after_callbacks]
|
11
18
|
base.send :attr_accessor, *[:before_callbacks, :after_callbacks]
|
12
19
|
base.extend ClassMethods
|
@@ -16,239 +23,41 @@ module SuperCallbacks
|
|
16
23
|
base.send :prepend, Prepended.new
|
17
24
|
end
|
18
25
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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.to_sym]
|
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.to_sym)
|
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) || {}
|
26
|
+
private
|
27
|
+
|
28
|
+
def self.puts_warning_messages_when_methods_already_defined(base)
|
29
|
+
overriden_instance_methods = (
|
30
|
+
base.instance_methods(false) & (
|
31
|
+
(
|
32
|
+
SuperCallbacks::ClassAndInstanceMethods.instance_methods(false) +
|
33
|
+
SuperCallbacks::ClassAndInstanceMethods.private_instance_methods(false)
|
34
|
+
) |
|
35
|
+
(
|
36
|
+
SuperCallbacks::InstanceMethods.instance_methods(false) +
|
37
|
+
SuperCallbacks::InstanceMethods.private_instance_methods(false)
|
176
38
|
)
|
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
39
|
)
|
40
|
+
).sort
|
185
41
|
|
186
|
-
|
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
|
42
|
+
unless overriden_instance_methods.empty?
|
43
|
+
puts "WARN: SuperCallbacks will override #{base} the following already existing instance methods: #{overriden_instance_methods}"
|
203
44
|
end
|
204
45
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
46
|
+
overriden_class_methods = (
|
47
|
+
base.methods(false) & (
|
48
|
+
(
|
49
|
+
SuperCallbacks::ClassAndInstanceMethods.instance_methods(false) +
|
50
|
+
SuperCallbacks::ClassAndInstanceMethods.private_instance_methods(false)
|
51
|
+
) |
|
52
|
+
(
|
53
|
+
SuperCallbacks::ClassMethods.instance_methods(false) +
|
54
|
+
SuperCallbacks::ClassMethods.private_instance_methods(false)
|
212
55
|
)
|
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
56
|
)
|
57
|
+
).sort
|
221
58
|
|
222
|
-
|
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
|
59
|
+
unless overriden_class_methods.empty?
|
60
|
+
puts "WARN: SuperCallbacks will override #{base} the following already existing class methods: #{overriden_class_methods}"
|
252
61
|
end
|
253
62
|
end
|
254
63
|
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module SuperCallbacks
|
2
|
+
# The methods defined here will be available "class" and "instance" for any class where SuperCallbacks is included
|
3
|
+
module ClassAndInstanceMethods
|
4
|
+
def before!(method_name, *remaining_args, &callback_proc)
|
5
|
+
raise ArgumentError, "`#{method_name}` is not or not yet defined for #{self}" unless method_defined? method_name
|
6
|
+
before(method_name, *remaining_args, &callback_proc)
|
7
|
+
end
|
8
|
+
|
9
|
+
def after!(method_name, *remaining_args, &callback_proc)
|
10
|
+
raise ArgumentError, "`#{method_name}` is not or not yet defined for #{self}" unless method_defined? method_name
|
11
|
+
after(method_name, *remaining_args, &callback_proc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def before(method_name, callback_method_name = nil, options = {}, &callback_proc)
|
15
|
+
callback_method_name_or_proc = callback_proc || callback_method_name
|
16
|
+
unless [Symbol, String, Proc].any? { |klass| callback_method_name_or_proc.is_a? klass }
|
17
|
+
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `method_name`, but is #{callback_method_name_or_proc.class}"
|
18
|
+
end
|
19
|
+
|
20
|
+
invalid_option_keys = options.keys - VALID_OPTION_KEYS
|
21
|
+
unless invalid_option_keys.empty?
|
22
|
+
raise ArgumentError, "Invalid `options` keys: #{invalid_option_keys}. Valid are only: #{VALID_OPTION_KEYS}"
|
23
|
+
end
|
24
|
+
if options[:if] && !([Symbol, String, Proc].any? { |klass| callback_method_name_or_proc.is_a? klass })
|
25
|
+
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `options[:if]`, but is #{options[:if].class}"
|
26
|
+
end
|
27
|
+
|
28
|
+
self.before_callbacks ||= {}
|
29
|
+
self.before_callbacks[method_name.to_sym] ||= []
|
30
|
+
self.before_callbacks[method_name.to_sym] << [callback_method_name_or_proc, options[:if]]
|
31
|
+
|
32
|
+
_callbacks_prepended_module_instance = callbacks_prepended_module_instance
|
33
|
+
|
34
|
+
# dont redefine, to save cpu cycles
|
35
|
+
unless _callbacks_prepended_module_instance.method_defined? method_name
|
36
|
+
_callbacks_prepended_module_instance.send(:define_method, method_name) do |*args|
|
37
|
+
begin
|
38
|
+
# refactored to use Thread.current for thread-safetiness
|
39
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change] ||= {}
|
40
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change][object_id] ||= []
|
41
|
+
|
42
|
+
all_instance_variables_before_change = instance_variables.each_with_object({}) do |instance_variable, hash|
|
43
|
+
hash[instance_variable] = instance_variable_get(instance_variable)
|
44
|
+
end
|
45
|
+
|
46
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change][object_id] << all_instance_variables_before_change
|
47
|
+
|
48
|
+
run_before_callbacks(method_name, *args)
|
49
|
+
super_value = super(*args)
|
50
|
+
run_after_callbacks(method_name, *args)
|
51
|
+
ensure
|
52
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change][object_id].pop
|
53
|
+
|
54
|
+
if Thread.current[:super_callbacks_all_instance_variables_before_change][object_id].empty?
|
55
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change].delete(object_id)
|
56
|
+
end
|
57
|
+
|
58
|
+
if Thread.current[:super_callbacks_all_instance_variables_before_change].empty?
|
59
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change] = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
super_value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def after(method_name, callback_method_name = nil, options = {}, &callback_proc)
|
69
|
+
callback_method_name_or_proc = callback_proc || callback_method_name
|
70
|
+
unless [Symbol, String, Proc].include? callback_method_name_or_proc.class
|
71
|
+
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `method_name`, but is #{callback_method_name_or_proc.class}"
|
72
|
+
end
|
73
|
+
|
74
|
+
invalid_option_keys = options.keys - VALID_OPTION_KEYS
|
75
|
+
unless invalid_option_keys.empty?
|
76
|
+
raise ArgumentError, "Invalid `options` keys: #{invalid_option_keys}. Valid are only: #{VALID_OPTION_KEYS}"
|
77
|
+
end
|
78
|
+
if options[:if] && ![Symbol, String, Proc].include?(options[:if].class)
|
79
|
+
raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `options[:if]`, but is #{options[:if].class}"
|
80
|
+
end
|
81
|
+
|
82
|
+
self.after_callbacks ||= {}
|
83
|
+
self.after_callbacks[method_name.to_sym] ||= []
|
84
|
+
self.after_callbacks[method_name.to_sym] << [callback_method_name_or_proc, options[:if]]
|
85
|
+
|
86
|
+
_callbacks_prepended_module_instance = callbacks_prepended_module_instance
|
87
|
+
|
88
|
+
# dont redefine, to save cpu cycles
|
89
|
+
unless _callbacks_prepended_module_instance.method_defined? method_name
|
90
|
+
_callbacks_prepended_module_instance.send(:define_method, method_name) do |*args|
|
91
|
+
begin
|
92
|
+
# refactored to use Thread.current for thread-safetiness
|
93
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change] ||= {}
|
94
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change][object_id] ||= []
|
95
|
+
|
96
|
+
all_instance_variables_before_change = instance_variables.each_with_object({}) do |instance_variable, hash|
|
97
|
+
hash[instance_variable] = instance_variable_get(instance_variable)
|
98
|
+
end
|
99
|
+
|
100
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change][object_id] << all_instance_variables_before_change
|
101
|
+
|
102
|
+
run_before_callbacks(method_name, *args)
|
103
|
+
super_value = super(*args)
|
104
|
+
run_after_callbacks(method_name, *args)
|
105
|
+
ensure
|
106
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change][object_id].pop
|
107
|
+
|
108
|
+
if Thread.current[:super_callbacks_all_instance_variables_before_change][object_id].empty?
|
109
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change].delete(object_id)
|
110
|
+
end
|
111
|
+
|
112
|
+
if Thread.current[:super_callbacks_all_instance_variables_before_change].empty?
|
113
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change] = nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
super_value
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def instance_variables_before_change
|
123
|
+
raise 'You cannot call this method outside the SuperCallback cycle' if Thread.current[:super_callbacks_all_instance_variables_before_change].nil?
|
124
|
+
Thread.current[:super_callbacks_all_instance_variables_before_change][object_id].last
|
125
|
+
end
|
126
|
+
|
127
|
+
def instance_variable_before_change(instance_variable)
|
128
|
+
raise 'You cannot call this method outside the SuperCallback cycle' if Thread.current[:super_callbacks_all_instance_variables_before_change].nil?
|
129
|
+
raise ArgumentError, "#{instance_variable} should be a string that starts with `@`" unless instance_variable.to_s.start_with? '@'
|
130
|
+
instance_variables_before_change[instance_variable.to_sym]
|
131
|
+
end
|
132
|
+
|
133
|
+
def instance_variable_changed?(instance_variable)
|
134
|
+
raise 'You cannot call this method outside the SuperCallback cycle' if Thread.current[:super_callbacks_all_instance_variables_before_change].nil?
|
135
|
+
raise ArgumentError, "#{instance_variable} should be a string that starts with `@`" unless instance_variable.to_s.start_with? '@'
|
136
|
+
|
137
|
+
before_change_value = instance_variable_before_change(instance_variable.to_sym)
|
138
|
+
current_value = instance_variable_get(instance_variable)
|
139
|
+
before_change_value != current_value
|
140
|
+
end
|
141
|
+
|
142
|
+
# TODO
|
143
|
+
# def around
|
144
|
+
# end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module SuperCallbacks
|
2
|
+
# The methods defined here will be available "class" methods for any class where SuperCallbacks is included
|
3
|
+
module ClassMethods
|
4
|
+
private
|
5
|
+
|
6
|
+
def callbacks_prepended_module_instance
|
7
|
+
ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SuperCallbacks
|
2
|
+
module Helpers
|
3
|
+
# (modified) File activesupport/lib/active_support/core_ext/hash/deep_merge.rb, line 18
|
4
|
+
def self.deep_merge_hashes_and_combine_arrays(this_hash, other_hash, &block)
|
5
|
+
self.deep_merge_hashes_and_combine_arrays!(this_hash.dup, other_hash, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
# (modified) File activesupport/lib/active_support/core_ext/hash/deep_merge.rb, line 23
|
9
|
+
def self.deep_merge_hashes_and_combine_arrays!(this_hash, other_hash, &block)
|
10
|
+
this_hash.merge!(other_hash) do |key, this_val, other_val|
|
11
|
+
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
12
|
+
self.deep_merge_hashes(this_val, other_val, &block)
|
13
|
+
elsif this_val.is_a?(Array) && other_val.is_a?(Array)
|
14
|
+
this_val + other_val
|
15
|
+
elsif block_given?
|
16
|
+
block.call(key, this_val, other_val)
|
17
|
+
else
|
18
|
+
other_val
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module SuperCallbacks
|
2
|
+
# The methods defined here will be available "class" methods for any class where SuperCallbacks is included
|
3
|
+
module InstanceMethods
|
4
|
+
# TODO: optimize by instead of dynamically getting all_ancestral_after_callbacks on runtime
|
5
|
+
# set them immediately when `include` is called on Base class
|
6
|
+
def run_before_callbacks(method_name, *args)
|
7
|
+
all_ancestral_before_callbacks = self.class.ancestors.reverse.each_with_object({}) do |ancestor, hash|
|
8
|
+
SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays!(
|
9
|
+
hash,
|
10
|
+
ancestor.instance_variable_get(:@before_callbacks) || {}
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
singleton_class_before_callbacks = instance_variable_get(:@before_callbacks) || {}
|
15
|
+
|
16
|
+
all_before_callbacks = SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays(
|
17
|
+
all_ancestral_before_callbacks,
|
18
|
+
singleton_class_before_callbacks
|
19
|
+
)
|
20
|
+
|
21
|
+
all_before_callbacks_on_method = all_before_callbacks[method_name] || []
|
22
|
+
|
23
|
+
all_before_callbacks_on_method.each do |before_callback, options_if|
|
24
|
+
is_condition_truthy = true
|
25
|
+
|
26
|
+
if options_if
|
27
|
+
is_condition_truthy = instance_exec *args, &options_if
|
28
|
+
end
|
29
|
+
|
30
|
+
if is_condition_truthy
|
31
|
+
if before_callback.is_a? Proc
|
32
|
+
instance_exec *args, &before_callback
|
33
|
+
else
|
34
|
+
send before_callback
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: optimize by instead of dynamically getting all_ancestral_after_callbacks on runtime
|
41
|
+
# set them immediately when `include` is called on Base class
|
42
|
+
def run_after_callbacks(method_name, *args)
|
43
|
+
all_ancestral_after_callbacks = self.class.ancestors.reverse.each_with_object({}) do |ancestor, hash|
|
44
|
+
SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays!(
|
45
|
+
hash,
|
46
|
+
ancestor.instance_variable_get(:@after_callbacks) || {}
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
singleton_class_after_callbacks = instance_variable_get(:@after_callbacks) || {}
|
51
|
+
|
52
|
+
all_after_callbacks = SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays(
|
53
|
+
all_ancestral_after_callbacks,
|
54
|
+
singleton_class_after_callbacks
|
55
|
+
)
|
56
|
+
|
57
|
+
all_after_callbacks_on_method = all_after_callbacks[method_name] || []
|
58
|
+
|
59
|
+
all_after_callbacks_on_method.each do |after_callback, options_if|
|
60
|
+
is_condition_truthy = true
|
61
|
+
|
62
|
+
if options_if
|
63
|
+
is_condition_truthy = instance_exec *args, &options_if
|
64
|
+
end
|
65
|
+
|
66
|
+
if is_condition_truthy
|
67
|
+
if after_callback.is_a? Proc
|
68
|
+
instance_exec *args, &after_callback
|
69
|
+
else
|
70
|
+
send after_callback
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def callbacks_prepended_module_instance
|
79
|
+
_callbacks_prepended_module_instance = self.singleton_class.ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
80
|
+
|
81
|
+
if _callbacks_prepended_module_instance.nil?
|
82
|
+
self.singleton_class.prepend SuperCallbacks::Prepended
|
83
|
+
_callbacks_prepended_module_instance = self.singleton_class.ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
|
84
|
+
end
|
85
|
+
|
86
|
+
_callbacks_prepended_module_instance
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# This is the module that will be prepended to the base (target) class where `include SuperCallbacks`
|
2
|
+
# have been called. This will be always a new instance of a Module, and will not be a permanent
|
3
|
+
# Module. This is important because, this module-instance is used by SuperCallbacks to define
|
4
|
+
# the methods whenever `before` or `after` is called. These defined methods here will then have a
|
5
|
+
# `super` inside it, which then will also call the "real" method defined in the target class.
|
6
|
+
module SuperCallbacks
|
7
|
+
class Prepended < Module
|
8
|
+
end
|
9
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: super_callbacks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jules Roman B. Polidario
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-08-
|
11
|
+
date: 2019-08-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -87,6 +87,11 @@ files:
|
|
87
87
|
- bin/console
|
88
88
|
- bin/setup
|
89
89
|
- lib/super_callbacks.rb
|
90
|
+
- lib/super_callbacks/class_and_instance_methods.rb
|
91
|
+
- lib/super_callbacks/class_methods.rb
|
92
|
+
- lib/super_callbacks/helpers.rb
|
93
|
+
- lib/super_callbacks/instance_methods.rb
|
94
|
+
- lib/super_callbacks/prepended.rb
|
90
95
|
- lib/super_callbacks/version.rb
|
91
96
|
- super_callbacks.gemspec
|
92
97
|
homepage: https://github.com/jrpolidario/super_callbacks
|