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 +7 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.travis.yml +11 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +356 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/super_callbacks.rb +232 -0
- data/lib/super_callbacks/version.rb +3 -0
- data/super_callbacks.gemspec +27 -0
- metadata +117 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
|
+
[](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
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,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,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: []
|