super_callbacks 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8f2916d0d7aafdfe6d62022e295eb35f19fe4366fe584adcefbdd7a856154853
4
+ data.tar.gz: 48e8b72a7e2dfbdc4109804062e60d62374dbcbcef42a42192991c7bb1a0a09b
5
+ SHA512:
6
+ metadata.gz: e381891c442fdf4f6fed1bee814a9fe20314880a05e1f8003da8df4f8b67be6b97c38b20a95c07acec4ed9faf706896e4de854f0665c8894adbd2ffceaa4d701
7
+ data.tar.gz: 8658ee29da8b6943a72c374c9df4efc9ebc81ba9ca023a7ff718644054c6b0a5c0b9b650c4efba9f590a9b783568676e8a127dfe8c86b494961a915ab6e317fd
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ .byebug_history
14
+
15
+ *.gem
16
+
17
+ Gemfile.lock
18
+ gemfiles/*.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.0.0
7
+ - 2.2.7
8
+ - 2.3.4
9
+ - 2.4.1
10
+ - 2.5.5
11
+ before_install: gem install bundler -v 2.0.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in super_callbacks.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Jules Roman B. Polidario
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # SuperCallbacks
2
+
3
+ [![Build Status](https://travis-ci.org/jrpolidario/super_callbacks.svg?branch=master)](https://travis-ci.org/jrpolidario/super_callbacks)
4
+
5
+ * Allows `before` and `after` callbacks to any Class.
6
+ * Supports both class and instance level callbacks
7
+ * Supports conditional callbacks
8
+ * Supports inherited callbacks; hence named "Super", get it? :D haha!
9
+
10
+ * Focuses on performance and flexibility as intended primarily for game development, and event-driven apps
11
+ * Standalone; no other gem dependencies
12
+ * `super_callbacks` is the upgraded version of my other repo [`dragon_ruby_callbacks`](https://github.com/jrpolidario/dragonruby_callbacks)
13
+ * Heavily influenced by [Rails' ActiveSupport::Callbacks](https://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html)
14
+
15
+ ## Dependencies
16
+
17
+ * **Ruby ~> 2.0**
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ gem 'super_callbacks', '~> 1.0'
25
+ ```
26
+
27
+ And then execute:
28
+
29
+ $ bundle
30
+
31
+ Or install it yourself as:
32
+
33
+ $ gem install super_callbacks
34
+
35
+ ## Usage
36
+
37
+ ### Example 1 (Block Mode)
38
+
39
+ ```ruby
40
+ require 'super_callbacks'
41
+
42
+ class Foo
43
+ # add this line inside your Class file/s
44
+ include SuperCallbacks
45
+
46
+ # add this block of lines
47
+ before :bar do
48
+ puts 'before bar!'
49
+ end
50
+
51
+ def bar
52
+ puts 'bar!'
53
+ end
54
+ end
55
+
56
+ foo = Foo.new
57
+ foo.bar
58
+ # => 'before bar!'
59
+ # => 'bar!'
60
+ ```
61
+
62
+ *Notice above that the before block gets called first before the method `bar`*
63
+
64
+ ```ruby
65
+ class Foo
66
+ include SuperCallbacks
67
+
68
+ after :bar do
69
+ puts 'after bar!'
70
+ end
71
+
72
+ def bar
73
+ puts 'bar!'
74
+ end
75
+ end
76
+
77
+ foo = Foo.new
78
+ foo.bar
79
+ # => 'bar!'
80
+ # => 'after bar!'
81
+ ```
82
+
83
+ *Notice above that the after block gets called after the method `bar`*
84
+
85
+ ### Example 2 (Method Calling)
86
+
87
+ ```ruby
88
+ class Foo
89
+ include SuperCallbacks
90
+
91
+ before :bar, :baz
92
+
93
+ def bar
94
+ puts 'bar!'
95
+ end
96
+
97
+ def baz
98
+ puts 'baz!'
99
+ end
100
+ end
101
+
102
+ foo = Foo.new
103
+ foo.bar
104
+ # => 'baz!'
105
+ # => 'bar!'
106
+ ```
107
+
108
+ *Notice above that you can also call another method instead of supplying a block.*
109
+ *Above uses `before`, but works similarly with `after`*
110
+
111
+ ### Example 3 (Multiple Callbacks)
112
+
113
+ ```ruby
114
+ class Foo
115
+ include SuperCallbacks
116
+
117
+ before :bar, :baz_1
118
+ before :bar do
119
+ puts 'baz 2!'
120
+ end
121
+ before :bar, :baz_3
122
+
123
+ def bar
124
+ puts 'bar!'
125
+ end
126
+
127
+ def baz_1
128
+ puts 'baz 1!'
129
+ end
130
+
131
+ def baz_3
132
+ puts 'baz 3!'
133
+ end
134
+ end
135
+
136
+ foo = Foo.new
137
+ foo.bar
138
+ # => 'baz 1!'
139
+ # => 'bar 2!'
140
+ # => 'bar 3!'
141
+ # => 'bar!'
142
+ ```
143
+
144
+ *Notice above multiple callbacks are supported, and that they are called in firt-come-first-served order.*
145
+ *Above uses `before`, but works similarly with `after`*
146
+
147
+ ### Example 4 (Setter Method Callbacks)
148
+
149
+ > This is the primary reason why I made this game: to handle "change-dependent" logic in my game engine
150
+
151
+ ```ruby
152
+ class Foo
153
+ include SuperCallbacks
154
+
155
+ attr_accessor :bar
156
+
157
+ before :bar= do |arg|
158
+ puts "@bar currently has a value of #{@bar}"
159
+ puts "@bar will have a new value of #{arg}"
160
+ end
161
+
162
+ before :baz do |arg1, arg2|
163
+ puts "baz will be called with arguments #{arg1}, #{arg2}"
164
+ end
165
+
166
+ def baz(x, y)
167
+ puts 'baz has been called!'
168
+ end
169
+ end
170
+
171
+ foo = Foo.new
172
+ foo.bar = 5
173
+ # => '@bar currently has a value of '
174
+ # => '@bar will have a new value of 5'
175
+ puts foo.bar
176
+ # => 5
177
+
178
+ foo.baz(1, 2)
179
+ # => 'baz will be called with arguments 1, 2'
180
+ # => 'baz has been called!'
181
+ ```
182
+
183
+ *Above uses `before`, but works similarly with `after`*
184
+
185
+ ### Example 5 (Conditional Callbacks)
186
+
187
+ ```ruby
188
+ class Monster
189
+ include SuperCallbacks
190
+
191
+ attr_accessor :hp
192
+
193
+ after :hp=, :despawn, if: -> (arg) { @hp == 0 }
194
+
195
+ # above is just equivalently:
196
+ # after :hp= do |arg|
197
+ # despawn if @hp == 0
198
+ # end
199
+
200
+ def despawn
201
+ puts 'despawning!'
202
+ # do something here, like say removing the Monster from the world
203
+ end
204
+ end
205
+
206
+ monster = Monster.new
207
+ monster.hp = 5
208
+ monster.hp -= 1 # 4
209
+ monster.hp -= 1 # 3
210
+ monster.hp -= 1 # 2
211
+ monster.hp -= 1 # 1
212
+ monster.hp -= 1 # hp is now 0, so despawn!
213
+ # => despawning!
214
+ ```
215
+
216
+ *Above uses `after`, but works similarly with `before`*
217
+
218
+ ### Example 6 (Pseudo-Skipping Callbacks)
219
+
220
+ * via Ruby's [`instance_variable_get`](https://ruby-doc.org/core-1.9.1/Object.html#method-i-instance_variable_get) and [`instance_variable_set`](https://ruby-doc.org/core-1.9.1/Object.html#method-i-instance_variable_set)
221
+
222
+ ```ruby
223
+ class Foo
224
+ include SuperCallbacks
225
+
226
+ attr_accessor :bar
227
+
228
+ before :bar= do |arg|
229
+ puts 'before bar= is called!'
230
+ end
231
+ end
232
+
233
+ foo = Foo.new
234
+
235
+ # normal way (callbacks are called):
236
+ foo.bar = 'somevalue'
237
+ # => 'before_bar= is called!'
238
+
239
+ # but to "pseudo" skip all callbacks, and directly manipulate the instance variable value:
240
+ foo.instance_variable_set(:@bar, 'somevalue')
241
+ ```
242
+
243
+ *At the moment, I am not compelled (yet?) to fully support skipping callbacks because I do not want to pollute the DSL and I do not find myself yet needing such behaviour, because the callbacks are there for "integrity". If I really want the callbacks conditional, I'll just use the conditional argument.*
244
+
245
+ ### Example 7 (Class and Instance Level Callbacks)
246
+
247
+ ```ruby
248
+ class Foo
249
+ include SuperCallbacks
250
+
251
+ before :bar do
252
+ puts 'before bar 1!'
253
+ end
254
+
255
+ before :bar do
256
+ puts 'before bar 2!'
257
+ end
258
+
259
+ def bar
260
+ puts 'bar!'
261
+ end
262
+ end
263
+
264
+ foo_1 = Foo.new
265
+ foo_2 = Foo.new
266
+
267
+ foo_1.before :bar do
268
+ puts 'before bar 3'
269
+ end
270
+
271
+ foo_1.before :bar do
272
+ puts 'before bar 4'
273
+ end
274
+
275
+ foo_1.bar
276
+ # => 'before bar 1!'
277
+ # => 'before bar 2!'
278
+ # => 'before bar 3'
279
+ # => 'before bar 4'
280
+ # => 'bar!'
281
+
282
+ foo_2.bar
283
+ # => 'before bar 1!'
284
+ # => 'before bar 2!'
285
+ # => 'bar!'
286
+ ```
287
+
288
+ *Notice above that foo_1 and foo_2 both call the class-level callbacks, while they have independent (not-shared) instance-level callbacks defined: foo_2. Order of execution is class-level first then instance-level, of which defined callbacks are then in order of first-come-first-serve.*
289
+ *Above uses `before`, but works similarly with `after`*
290
+
291
+ ### Example 8 (Inherited Callbacks)
292
+
293
+ ```ruby
294
+ class Foo
295
+ include SuperCallbacks
296
+
297
+ before :bar do
298
+ puts 'Foo: before bar 1!'
299
+ end
300
+
301
+ def bar
302
+ puts 'bar!'
303
+ end
304
+ end
305
+
306
+ class SubFoo < Foo
307
+ before :bar do
308
+ puts 'SubFoo: bar'
309
+ end
310
+ end
311
+
312
+ foo = Foo.new
313
+ foo.bar
314
+ # => 'Foo: before bar 1!'
315
+ # => 'bar!'
316
+
317
+ sub_foo = SubFoo.new
318
+ sub_foo.bar
319
+ # => 'SubFoo: bar'
320
+ # => 'Foo: before bar 1!'
321
+ # => 'bar!'
322
+ ```
323
+
324
+ *Notice above `sub_foo` calls both `before` callbacks defined in `Foo` and `SubFoo`, because SubFoo inherits from Foo. Callbacks are called in order of ancestors ascending; meaning it starts calling the instance's class (SubFoo) callbacks first, then the superclass's callbacks, and the remaining ancestor classes, etc...*
325
+ *Above uses `before`, but works similarly with `after`*
326
+
327
+
328
+ ## TODOs
329
+ * when the need already arises, implement `around` (If you have ideas or want to help this part, please feel free to fork or send me a message! :)
330
+
331
+ ## Development
332
+
333
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
334
+
335
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
336
+
337
+ ## Contributing
338
+
339
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jrpolidario/super_callbacks. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
340
+
341
+ ## License
342
+
343
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
344
+
345
+ ## Changelog
346
+
347
+ * 1.0.0 (2019-08-12)
348
+ * Cleaner code without explicitly calling `run_callbacks` anymore; done now because of ruby upgrade from 1.9 to 2.0+ which already supports `prepend`
349
+ * Supported both class and instance level callbacks
350
+ * Supported inherited callbacks
351
+ * v0.2.1 (2019-08-09) *From `dragonruby_callbacks`*
352
+ * Fixed syntax errors for ruby 1.9.3; Fixed not supporting subclasses of Proc, String, or Symbol
353
+ * v0.2 (2019-08-08) *From `dragonruby_callbacks`*
354
+ * Supported [conditional callbacks](#conditional-callbacks) with `:if`
355
+ * v0.1 (2019-08-07) *From `dragonruby_callbacks`*
356
+ * Done all
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "super_callbacks"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,232 @@
1
+ require 'super_callbacks/version'
2
+
3
+ module SuperCallbacks
4
+ VERSION = '1.0.0'.freeze
5
+
6
+ VALID_OPTION_KEYS = [:if].freeze
7
+
8
+ def self.included(base)
9
+ base.singleton_class.attr_accessor :before_callbacks, :after_callbacks
10
+ base.attr_accessor :before_callbacks, :after_callbacks
11
+ base.extend ClassMethods
12
+ base.include InstanceMethods
13
+ base.extend ClassAndInstanceMethods
14
+ base.include ClassAndInstanceMethods
15
+ base.prepend Prepended.new
16
+ end
17
+
18
+ class Prepended < Module
19
+ end
20
+
21
+ module Helpers
22
+ # (modified) File activesupport/lib/active_support/core_ext/hash/deep_merge.rb, line 18
23
+ def self.deep_merge_hashes(this_hash, other_hash, &block)
24
+ deep_merge_hashes!(this_hash.dup, other_hash, &block)
25
+ end
26
+
27
+ # (modified) File activesupport/lib/active_support/core_ext/hash/deep_merge.rb, line 23
28
+ def self.deep_merge_hashes!(this_hash, other_hash, &block)
29
+ this_hash.merge!(other_hash) do |key, this_val, other_val|
30
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
31
+ self.deep_merge_hashes(this_val, other_val, &block)
32
+ elsif block_given?
33
+ block.call(key, this_val, other_val)
34
+ else
35
+ other_val
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.deep_merge_hashes_and_combine_arrays(this_hash, other_hash, &block)
41
+ self.deep_merge_hashes_and_combine_arrays!(this_hash.dup, other_hash, &block)
42
+ end
43
+
44
+ def self.deep_merge_hashes_and_combine_arrays!(this_hash, other_hash, &block)
45
+ this_hash.merge!(other_hash) do |key, this_val, other_val|
46
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
47
+ self.deep_merge_hashes(this_val, other_val, &block)
48
+ elsif this_val.is_a?(Array) && other_val.is_a?(Array)
49
+ this_val + other_val
50
+ elsif block_given?
51
+ block.call(key, this_val, other_val)
52
+ else
53
+ other_val
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ module ClassAndInstanceMethods
60
+ def before!(method_name, *remaining_args)
61
+ raise ArgumentError, "`#{method_name}` is not or not yet defined for #{self}" unless method_defined? method_name
62
+ before(method_name, *remaining_args)
63
+ end
64
+
65
+ def after!(method_name, *remaining_args)
66
+ raise ArgumentError, "`#{method_name}` is not or not yet defined for #{self}" unless method_defined? method_name
67
+ before(method_name, *remaining_args)
68
+ end
69
+
70
+ def before(method_name, callback_method_name = nil, options = {}, &callback_proc)
71
+ callback_method_name_or_proc = callback_proc || callback_method_name
72
+ unless [Symbol, String, Proc].any? { |klass| callback_method_name_or_proc.is_a? klass }
73
+ raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `method_name`, but is #{callback_method_name_or_proc.class}"
74
+ end
75
+
76
+ invalid_option_keys = options.keys - VALID_OPTION_KEYS
77
+ unless invalid_option_keys.empty?
78
+ raise ArgumentError, "Invalid `options` keys: #{invalid_option_keys}. Valid are only: #{VALID_OPTION_KEYS}"
79
+ end
80
+ if options[:if] && !([Symbol, String, Proc].any? { |klass| callback_method_name_or_proc.is_a? klass })
81
+ raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `options[:if]`, but is #{options[:if].class}"
82
+ end
83
+
84
+ self.before_callbacks ||= {}
85
+ self.before_callbacks[method_name.to_sym] ||= []
86
+ self.before_callbacks[method_name.to_sym] << [callback_method_name_or_proc, options[:if]]
87
+
88
+ _callbacks_prepended_module_instance = callbacks_prepended_module_instance
89
+
90
+ # dont redefine, to save cpu cycles
91
+ unless callbacks_prepended_module_instance.method_defined? method_name
92
+ callbacks_prepended_module_instance.define_method method_name do |*args|
93
+ run_before_callbacks(method_name, *args)
94
+ super_value = super(*args)
95
+ run_after_callbacks(method_name, *args)
96
+ super_value
97
+ end
98
+ end
99
+ end
100
+
101
+ def after(method_name, callback_method_name = nil, options = {}, &callback_proc)
102
+ callback_method_name_or_proc = callback_proc || callback_method_name
103
+ unless [Symbol, String, Proc].include? callback_method_name_or_proc.class
104
+ raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `method_name`, but is #{callback_method_name_or_proc.class}"
105
+ end
106
+
107
+ invalid_option_keys = options.keys - VALID_OPTION_KEYS
108
+ unless invalid_option_keys.empty?
109
+ raise ArgumentError, "Invalid `options` keys: #{invalid_option_keys}. Valid are only: #{VALID_OPTION_KEYS}"
110
+ end
111
+ if options[:if] && ![Symbol, String, Proc].include?(options[:if].class)
112
+ raise ArgumentError, "Only `Symbol`, `String` or `Proc` allowed for `options[:if]`, but is #{options[:if].class}"
113
+ end
114
+
115
+ self.after_callbacks ||= {}
116
+ self.after_callbacks[method_name.to_sym] ||= []
117
+ self.after_callbacks[method_name.to_sym] << [callback_method_name_or_proc, options[:if]]
118
+
119
+ _callbacks_prepended_module_instance = callbacks_prepended_module_instance
120
+
121
+ # dont redefine, to save cpu cycles
122
+ unless _callbacks_prepended_module_instance.method_defined? method_name
123
+ _callbacks_prepended_module_instance.define_method method_name do |*args|
124
+ run_before_callbacks(method_name, *args)
125
+ super_value = super(*args)
126
+ run_after_callbacks(method_name, *args)
127
+ super_value
128
+ end
129
+ end
130
+ end
131
+
132
+ # TODO
133
+ # def around
134
+ # end
135
+ end
136
+
137
+ module ClassMethods
138
+
139
+ private
140
+
141
+ def callbacks_prepended_module_instance
142
+ ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
143
+ end
144
+ end
145
+
146
+ module InstanceMethods
147
+ # TODO: optimize by instead of dynamically getting all_ancestral_after_callbacks on runtime
148
+ # set them immediately when `include` is called on Base class
149
+ def run_before_callbacks(method_name, *args)
150
+ all_ancestral_before_callbacks = self.class.ancestors.each_with_object({}) do |ancestor, hash|
151
+ SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays!(
152
+ hash,
153
+ ancestor.instance_variable_get(:@before_callbacks) || {}
154
+ )
155
+ end
156
+
157
+ singleton_class_before_callbacks = instance_variable_get(:@before_callbacks) || {}
158
+
159
+ all_before_callbacks = SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays(
160
+ all_ancestral_before_callbacks,
161
+ singleton_class_before_callbacks
162
+ )
163
+
164
+ all_before_callbacks_on_method = all_before_callbacks[method_name] || []
165
+
166
+ all_before_callbacks_on_method.each do |before_callback, options_if|
167
+ is_condition_truthy = true
168
+
169
+ if options_if
170
+ is_condition_truthy = instance_exec *args, &options_if
171
+ end
172
+
173
+ if is_condition_truthy
174
+ if before_callback.is_a? Proc
175
+ instance_exec *args, &before_callback
176
+ else
177
+ send before_callback
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ # TODO: optimize by instead of dynamically getting all_ancestral_after_callbacks on runtime
184
+ # set them immediately when `include` is called on Base class
185
+ def run_after_callbacks(method_name, *args)
186
+ all_ancestral_after_callbacks = self.class.ancestors.each_with_object({}) do |ancestor, hash|
187
+ SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays!(
188
+ hash,
189
+ ancestor.instance_variable_get(:@after_callbacks) || {}
190
+ )
191
+ end
192
+
193
+ singleton_class_after_callbacks = instance_variable_get(:@after_callbacks) || {}
194
+
195
+ all_after_callbacks = SuperCallbacks::Helpers.deep_merge_hashes_and_combine_arrays(
196
+ all_ancestral_after_callbacks,
197
+ singleton_class_after_callbacks
198
+ )
199
+
200
+ all_after_callbacks_on_method = all_after_callbacks[method_name] || []
201
+
202
+ all_after_callbacks_on_method.each do |after_callback, options_if|
203
+ is_condition_truthy = true
204
+
205
+ if options_if
206
+ is_condition_truthy = instance_exec *args, &options_if
207
+ end
208
+
209
+ if is_condition_truthy
210
+ if after_callback.is_a? Proc
211
+ instance_exec *args, &after_callback
212
+ else
213
+ send after_callback
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ private
220
+
221
+ def callbacks_prepended_module_instance
222
+ _callbacks_prepended_module_instance = self.singleton_class.ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
223
+
224
+ if _callbacks_prepended_module_instance.nil?
225
+ self.singleton_class.prepend SuperCallbacks::Prepended
226
+ _callbacks_prepended_module_instance = self.singleton_class.ancestors.reverse.detect { |ancestor| ancestor.is_a? SuperCallbacks::Prepended }
227
+ end
228
+
229
+ _callbacks_prepended_module_instance
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,3 @@
1
+ module SuperCallbacks
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'super_callbacks/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'super_callbacks'
7
+ spec.version = SuperCallbacks::VERSION
8
+ spec.authors = ['Jules Roman B. Polidario']
9
+ spec.email = ['jules@topfloor.ie']
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.'
13
+ spec.homepage = 'https://github.com/jrpolidario/super_callbacks'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_development_dependency 'bundler', '~> 2.0'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '~> 3.0'
26
+ spec.add_development_dependency 'byebug', '~> 9.0'
27
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: super_callbacks
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jules Roman B. Polidario
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-08-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '9.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
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.
73
+ email:
74
+ - jules@topfloor.ie
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - ".gitignore"
80
+ - ".rspec"
81
+ - ".travis.yml"
82
+ - Gemfile
83
+ - Gemfile.lock
84
+ - LICENSE.txt
85
+ - README.md
86
+ - Rakefile
87
+ - bin/console
88
+ - bin/setup
89
+ - lib/super_callbacks.rb
90
+ - lib/super_callbacks/version.rb
91
+ - super_callbacks.gemspec
92
+ homepage: https://github.com/jrpolidario/super_callbacks
93
+ licenses:
94
+ - MIT
95
+ metadata: {}
96
+ post_install_message:
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project:
112
+ rubygems_version: 2.7.6.2
113
+ signing_key:
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.
117
+ test_files: []