super_callbacks 1.0.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 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: []