# SuperCallbacks [![Build Status](https://travis-ci.org/jrpolidario/super_callbacks.svg?branch=master)](https://travis-ci.org/jrpolidario/super_callbacks) * Allows `before` and `after` callbacks to any Class. * Supports both class and instance level callbacks * Supports conditional callbacks * Supports inherited callbacks; hence named "Super", get it? :D haha! --- * Focuses on performance and flexibility as intended primarily for game development, and event-driven apps * Standalone; no other gem dependencies * `super_callbacks` is the upgraded version of my other repo [`dragon_ruby_callbacks`](https://github.com/jrpolidario/dragonruby_callbacks) * Heavily influenced by [Rails' ActiveSupport::Callbacks](https://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html) ## Dependencies * **Ruby ~> 2.0** ## Installation Add this line to your application's Gemfile: ```ruby gem 'super_callbacks', '~> 1.0' ``` And then execute: $ bundle Or install it yourself as: $ gem install super_callbacks ## Usage ### Example 1 (Block Mode) ```ruby require 'super_callbacks' class Foo # add this line inside your Class file/s include SuperCallbacks # add this block of lines before :bar do puts 'before bar!' end def bar puts 'bar!' end end foo = Foo.new foo.bar # => 'before bar!' # => 'bar!' ``` *Notice above that the before block gets called first before the method `bar`* ```ruby class Foo include SuperCallbacks after :bar do puts 'after bar!' end def bar puts 'bar!' end end foo = Foo.new foo.bar # => 'bar!' # => 'after bar!' ``` *Notice above that the after block gets called after the method `bar`* ### Example 2 (Method Calling) ```ruby class Foo include SuperCallbacks before :bar, :baz def bar puts 'bar!' end def baz puts 'baz!' end end foo = Foo.new foo.bar # => 'baz!' # => 'bar!' ``` *Notice above that you can also call another method instead of supplying a block.* *Above uses `before`, but works similarly with `after`* ### Example 3 (Multiple Callbacks) ```ruby class Foo include SuperCallbacks before :bar, :baz_1 before :bar do puts 'baz 2!' end before :bar, :baz_3 def bar puts 'bar!' end def baz_1 puts 'baz 1!' end def baz_3 puts 'baz 3!' end end foo = Foo.new foo.bar # => 'baz 1!' # => 'bar 2!' # => 'bar 3!' # => 'bar!' ``` *Notice above multiple callbacks are supported, and that they are called in firt-come-first-served order.* *Above uses `before`, but works similarly with `after`* ### Example 4 (Setter Method Callbacks) > This is the primary reason why I made this game: to handle "change-dependent" logic in my game engine ```ruby class Foo include SuperCallbacks attr_accessor :bar before :bar= do |arg| puts "@bar currently has a value of #{@bar}" puts "@bar will have a new value of #{arg}" end before :baz do |arg1, arg2| puts "baz will be called with arguments #{arg1}, #{arg2}" end def baz(x, y) puts 'baz has been called!' end end foo = Foo.new foo.bar = 5 # => '@bar currently has a value of ' # => '@bar will have a new value of 5' puts foo.bar # => 5 foo.baz(1, 2) # => 'baz will be called with arguments 1, 2' # => 'baz has been called!' ``` *Above uses `before`, but works similarly with `after`* ### Example 5 (Conditional Callbacks) ```ruby class Monster include SuperCallbacks attr_accessor :hp after :hp=, :despawn, if: -> (arg) { @hp == 0 } # above is just equivalently: # after :hp= do |arg| # despawn if @hp == 0 # end def despawn puts 'despawning!' # do something here, like say removing the Monster from the world end end monster = Monster.new monster.hp = 5 monster.hp -= 1 # 4 monster.hp -= 1 # 3 monster.hp -= 1 # 2 monster.hp -= 1 # 1 monster.hp -= 1 # hp is now 0, so despawn! # => despawning! ``` *Above uses `after`, but works similarly with `before`* ### Example 6 (Pseudo-Skipping Callbacks) * 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) ```ruby class Foo include SuperCallbacks attr_accessor :bar before :bar= do |arg| puts 'before bar= is called!' end end foo = Foo.new # normal way (callbacks are called): foo.bar = 'somevalue' # => 'before_bar= is called!' # but to "pseudo" skip all callbacks, and directly manipulate the instance variable value: foo.instance_variable_set(:@bar, 'somevalue') ``` *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.* ### Example 7 (Class and Instance Level Callbacks) ```ruby class Foo include SuperCallbacks before :bar do puts 'before bar 1!' end before :bar do puts 'before bar 2!' end def bar puts 'bar!' end end foo_1 = Foo.new foo_2 = Foo.new foo_1.before :bar do puts 'before bar 3' end foo_1.before :bar do puts 'before bar 4' end foo_1.bar # => 'before bar 1!' # => 'before bar 2!' # => 'before bar 3' # => 'before bar 4' # => 'bar!' foo_2.bar # => 'before bar 1!' # => 'before bar 2!' # => 'bar!' ``` *Notice above that foo_1 and foo_2 both call the class-level callbacks, while they have independent (not-shared) instance-level callbacks defined. Order of execution is class-level first then instance-level, of which defined callbacks are then in order of first-come-first-serve.* *Above uses `before`, but works similarly with `after`* ### Example 8 (Inherited Callbacks) ```ruby class Foo include SuperCallbacks before :bar do puts 'Foo: before bar 1!' end def bar puts 'bar!' end end class SubFoo < Foo before :bar do puts 'SubFoo: bar' end end foo = Foo.new foo.bar # => 'Foo: before bar 1!' # => 'bar!' sub_foo = SubFoo.new sub_foo.bar # => 'Foo: before bar 1!' # => 'SubFoo: bar' # => 'bar!' ``` *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 descending; meaning it starts calling the top-level ancestor superclass callbacks, and then calling its subclass callbacks, until it reaches the instance's class callbacks* *Above uses `before`, but works similarly with `after`* ## TODOs * 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! :) ## Development 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. 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). ## Contributing 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. ## License The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). ## Changelog * 1.0.2 (2019-08-12) * Cleaner code without explicitly calling `run_callbacks` anymore; done now because of ruby upgrade from 1.9 to 2.0+ which already supports `prepend` * Supported both class and instance level callbacks * Supported inherited callbacks * v0.2.1 (2019-08-09) *From `dragonruby_callbacks`* * Fixed syntax errors for ruby 1.9.3; Fixed not supporting subclasses of Proc, String, or Symbol * v0.2 (2019-08-08) *From `dragonruby_callbacks`* * Supported [conditional callbacks](#conditional-callbacks) with `:if` * v0.1 (2019-08-07) *From `dragonruby_callbacks`* * Done all