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.
@@ -1,3 +1,3 @@
1
1
  module SuperCallbacks
2
- VERSION = '1.0.3'
2
+ VERSION = '1.1.0'
3
3
  end
@@ -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
- run_before_callbacks(method_name, *args)
79
- super_value = super(*args)
80
- run_after_callbacks(method_name, *args)
81
- super_value
82
- end
83
- end
84
- end
85
-
86
- def after(method_name, callback_method_name = nil, options = {}, &callback_proc)
87
- callback_method_name_or_proc = callback_proc || callback_method_name
88
- unless [Symbol, String, Proc].include? callback_method_name_or_proc.class
89
- raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `method_name`, but is #{callback_method_name_or_proc.class}"
90
- end
91
-
92
- invalid_option_keys = options.keys - VALID_OPTION_KEYS
93
- unless invalid_option_keys.empty?
94
- raise ArgumentError, "Invalid `options` keys: #{invalid_option_keys}. Valid are only: #{VALID_OPTION_KEYS}"
95
- end
96
- if options[:if] && ![Symbol, String, Proc].include?(options[:if].class)
97
- raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `options[:if]`, but is #{options[:if].class}"
98
- end
99
-
100
- self.after_callbacks ||= {}
101
- self.after_callbacks[method_name.to_sym] ||= []
102
- self.after_callbacks[method_name.to_sym] << [callback_method_name_or_proc, options[:if]]
103
-
104
- _callbacks_prepended_module_instance = callbacks_prepended_module_instance
105
-
106
- # dont redefine, to save cpu cycles
107
- unless _callbacks_prepended_module_instance.method_defined? method_name
108
- _callbacks_prepended_module_instance.send(:define_method, method_name) do |*args|
109
- run_before_callbacks(method_name, *args)
110
- super_value = super(*args)
111
- run_after_callbacks(method_name, *args)
112
- super_value
113
- end
114
- end
115
- end
116
-
117
- # TODO
118
- # def around
119
- # end
120
- end
121
-
122
- module ClassMethods
123
-
124
- private
125
-
126
- def callbacks_prepended_module_instance
127
- ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
128
- end
129
- end
130
-
131
- module InstanceMethods
132
- # TODO: optimize by instead of dynamically getting all_ancestral_after_callbacks on runtime
133
- # set them immediately when `include` is called on Base class
134
- def run_before_callbacks(method_name, *args)
135
- all_ancestral_before_callbacks = self.class.ancestors.reverse.each_with_object({}) do |ancestor, hash|
136
- SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays!(
137
- hash,
138
- ancestor.instance_variable_get(:@before_callbacks) || {}
139
- )
140
- end
141
-
142
- singleton_class_before_callbacks = instance_variable_get(:@before_callbacks) || {}
143
-
144
- all_before_callbacks = SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays(
145
- all_ancestral_before_callbacks,
146
- singleton_class_before_callbacks
147
- )
148
-
149
- all_before_callbacks_on_method = all_before_callbacks[method_name] || []
150
-
151
- all_before_callbacks_on_method.each do |before_callback, options_if|
152
- is_condition_truthy = true
153
-
154
- if options_if
155
- is_condition_truthy = instance_exec *args, &options_if
156
- end
157
-
158
- if is_condition_truthy
159
- if before_callback.is_a? Proc
160
- instance_exec *args, &before_callback
161
- else
162
- send before_callback
163
- end
164
- end
165
- end
166
- end
167
-
168
- # TODO: optimize by instead of dynamically getting all_ancestral_after_callbacks on runtime
169
- # set them immediately when `include` is called on Base class
170
- def run_after_callbacks(method_name, *args)
171
- all_ancestral_after_callbacks = self.class.ancestors.reverse.each_with_object({}) do |ancestor, hash|
172
- SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays!(
173
- hash,
174
- ancestor.instance_variable_get(:@after_callbacks) || {}
175
- )
176
- end
177
-
178
- singleton_class_after_callbacks = instance_variable_get(:@after_callbacks) || {}
179
-
180
- all_after_callbacks = SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays(
181
- all_ancestral_after_callbacks,
182
- singleton_class_after_callbacks
183
- )
184
-
185
- all_after_callbacks_on_method = all_after_callbacks[method_name] || []
186
-
187
- all_after_callbacks_on_method.each do |after_callback, options_if|
188
- is_condition_truthy = true
189
-
190
- if options_if
191
- is_condition_truthy = instance_exec *args, &options_if
192
- end
193
-
194
- if is_condition_truthy
195
- if after_callback.is_a? Proc
196
- instance_exec *args, &after_callback
197
- else
198
- send after_callback
199
- end
200
- end
201
- end
202
- end
203
-
204
- private
205
-
206
- def callbacks_prepended_module_instance
207
- _callbacks_prepended_module_instance = self.singleton_class.ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
208
-
209
- if _callbacks_prepended_module_instance.nil?
210
- self.singleton_class.prepend SuperCallbacks::Prepended
211
- _callbacks_prepended_module_instance = self.singleton_class.ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
212
- end
213
-
214
- _callbacks_prepended_module_instance
215
- end
216
- end
217
- end
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
@@ -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 both class and instance level callbacks, conditional callbacks, and inherited callbacks.'
12
- spec.description = 'Allows `before` and `after` callbacks to any Class. Supports both 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.'
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.3
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 both class
70
- and instance level callbacks, conditional callbacks, and inherited callbacks. Focuses
71
- on performance and flexibility as intended primarily for game development, and event-driven
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 both class and
116
- instance level callbacks, conditional callbacks, and inherited 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: []