├── .gitignore ├── .ruby-gemset ├── .ruby-version ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── Guardfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── after_do.gemspec ├── lib ├── after_do.rb └── after_do │ └── version.rb ├── samples ├── alternative_naming.rb ├── before.rb ├── dog.rb ├── error_in_callback.rb ├── getting_a_hold.rb ├── inheritance.rb ├── singleton.rb ├── with_module.rb └── within_class.rb └── spec ├── after_do_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | after_do 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.3 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.0 5 | - 2.1 6 | - 2.2 7 | - 2.3.3 8 | - 2.4.6 9 | - 2.5.5 10 | - 2.6.3 11 | - ruby-head 12 | - jruby-9.0.5.0 13 | - jruby-9.1.17.0 14 | - jruby-9.2.7.0 15 | - jruby-head 16 | matrix: 17 | allow_failures: 18 | - rvm: jruby-head 19 | - rvm: ruby-head 20 | cache: bundler 21 | before_script: 22 | - "bundle update" 23 | script: bundle exec rspec spec 24 | addons: 25 | code_climate: 26 | repo_token: 003492f2f00fb5d11e9cf945c5d88e44413800190183ee4fa0c97edff4fd553c 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.0 (unreleased) 2 | 3 | * Introduces AfterDo::AlternativeNaming to have all methods (`after`, `before`, `remove_all_callbacks`) prefixed with `ad_` to avoid naming conflicts for instance in ActionCable 4 | * get all core functionality into AfterDo::Core so people can write their own interfaces with own method names 5 | 6 | ## 0.4.0 (March 25th, 2016) 7 | 8 | * Hand new arguments to callbacks, namely method name and return value (for after). New Order of block arguments for before is: `arguments, method_name, object`, for after it is: `arguments, method_name, return_value, object`. To migrate to this version you need to change blocks that either use `*args` and then access `args` or calls that do `arg_1, arg_2, object` as the third argument is now the method name. You can change them to `arg_1, arg_2, *, object`. 9 | * Dropped explicit rubinius support, it should work but dropped testing. If you need it please get in touch. 10 | 11 | ## 0.3.1 (March 27th, 2014) 12 | 13 | * improve error reporting 14 | * run warning free with -w 15 | 16 | ## 0.3.0 (January 19th, 2014) 17 | 18 | * Work properly with inheritance (callbacks are just called if `super` is really invoked) 19 | * Work properly with modules (callbacks on module methods are executed when the methods are called) 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'bundler' 6 | gem 'rake' 7 | gem 'rspec' 8 | gem 'simplecov' 9 | gem "codeclimate-test-reporter" 10 | gem 'rdoc' 11 | 12 | RUBY_2 = /^2\./ 13 | unless RUBY_VERSION =~ RUBY_2 14 | # json 2 seems to be broken old rubies 15 | gem 'json', '< 2' 16 | end 17 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | guard :rspec do 5 | watch(%r{^spec/.+_spec\.rb$}) 6 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 7 | watch('spec/spec_helper.rb') { "spec" } 8 | end 9 | 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Tobias Pfeiffer 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # after_do [![Gem Version](https://badge.fury.io/rb/after_do.png)](http://badge.fury.io/rb/after_do)[![Build Status](https://travis-ci.org/PragTob/after_do.png?branch=master)](https://travis-ci.org/PragTob/after_do)[![Code Climate](https://codeclimate.com/github/PragTob/after_do.png)](https://codeclimate.com/github/PragTob/after_do)[![Coverage Status](https://coveralls.io/repos/PragTob/after_do/badge.png)](https://coveralls.io/r/PragTob/after_do) 2 | 3 | after_do is a simple gem, that helps you fight cross-cutting concerns with an approach similar to Aspect Oriented Programming (AOP). after_do allows you to execute blocks (callbacks) after/before specific methods of a class or a module are called. 4 | 5 | If the class extends `AfterDo` you can simply do this by 6 | 7 | ``` 8 | MyClass.after :some_method do whatever_you_want end 9 | # you can also do before 10 | MyClass.before :a_method do so_much end 11 | ``` 12 | 13 | Some facts about after_do: 14 | 15 | * no runtime dependencies 16 | * small code base: code is around 160 lines of code with blank lines, comments and everything - simplecov reports less than 80 relevant lines of code 17 | * simple DSL 18 | * no monkey patching 19 | 20 | ## Why would you want to do this? 21 | Well to fight cross-cutting concerns. These are concerns in an applications that apply to multiple objects (e.g. they cross-cut). 22 | A popular example is logging - you might want to log multiple actions of different classes but logging is not the primary concern of the class in question. With logging you litter all your code with logging statements - that concern is spread over many files and adds unnecessary noise to them. 23 | With after_do you could put all the logging in one file. Other use cases include gathering business statistics or redrawing timing of elements. 24 | This should generally not be done to alter behavior of the class and its instances - this makes programs more confusing rather than easier to understand. 25 | 26 | The idea for this is inspired by Aspect Oriented Programming - e.g. do something when specific methods are executed. However I doubt that this formally fulfills the lingo (join points, aspects, advice...) 27 | 28 | ## Installation 29 | 30 | Add this line to your application's Gemfile: 31 | 32 | gem 'after_do' 33 | 34 | And then execute: 35 | 36 | $ bundle 37 | 38 | Or install it yourself as: 39 | 40 | $ gem install after_do 41 | 42 | And then you have to require the gem before you can use it: 43 | 44 | require 'after_do' 45 | 46 | ## Usage 47 | 48 | This section is dedicated to show the general usage and effects of after_do. You can also check out the [samples directory](https://github.com/PragTob/after_do/tree/master/samples) or the [specs](https://github.com/PragTob/after_do/blob/master/spec/after_do_spec.rb) in the spec folder. 49 | 50 | ### General usage 51 | 52 | In order to use after_do the class/module you want to use it with first has to extend the `AfterDo` module. You can do this right in the class definition or afterwards like this: `MyClass.extend AfterDo`. 53 | With this setup you can add a callback to `method` like this: 54 | 55 | ```ruby 56 | MyClass.after :method do magic end 57 | ``` 58 | 59 | With after_do you can do simple things like printing something out every time a method is called as in this example: 60 | 61 | ```ruby 62 | class Dog 63 | def bark 64 | puts 'Woooof' 65 | end 66 | end 67 | 68 | Dog.extend AfterDo 69 | Dog.after :bark do puts 'I just heard a dog bark!' end 70 | 71 | dog = Dog.new 72 | dog2 = Dog.new 73 | 74 | dog.bark 75 | dog2.bark 76 | 77 | # Output is: 78 | # Woooof 79 | # I just heard a dog bark! 80 | # Woooof 81 | # I just heard a dog bark! 82 | ``` 83 | 84 | ### Getting a hold of the method arguments, the object and more! 85 | 86 | With after_do both the arguments to the method you are attaching the callback to and the object for which the callback is executed are passed into the callback block. 87 | 88 | So if you have a method that takes two arguments you can get those like this: 89 | 90 | ```ruby 91 | MyClass.after :two_arg_method do |argument_one, argument_2| something end 92 | ``` 93 | 94 | The method name is passed in as the third argument: 95 | 96 | ```ruby 97 | MyClass.before :two_arg_method do |argument_one, argument_2, method_name| 98 | something 99 | end 100 | ``` 101 | 102 | The method name is passed for convenience if you do logging for instance. If you start doing `if` or `case` on the method name, rather think about if you should use different callbacks for the different methods. 103 | 104 | Another argument that is passed in, but only for `after` is the return value of the method so you can do: 105 | 106 | ```ruby 107 | MyClass.after :two_arg_method do |arg1, arg2, name, return_value| 108 | report return_value 109 | end 110 | ``` 111 | 112 | The object itself is passed in as the last block argument finally so all arguments together are: 113 | 114 | ```ruby 115 | MyClass.after :method do |arg1, arg2, name, return_value, object| 116 | magic 117 | end 118 | 119 | # remember before doesn't have the return value 120 | MyClass.before :method do |arg1, arg2, name, object| 121 | other_magic 122 | end 123 | ``` 124 | 125 | Why this order of arguments? Well at first there are the arguments related to the method itself (arguments, name and optional return value) and then there is the object. 126 | 127 | Of course you can just grab the object, if you're not interested in all the method related data, by using the splat operator to _soak up_ all the other arguments: 128 | 129 | ```ruby 130 | MyClass.after :two_arg_method do |*, obj| fancy_stuff(obj) end 131 | ``` 132 | 133 | 134 | If you do not want to get a hold of the method arguments or the object, then you can just don't care about the block parameters and can leave them out :-) 135 | 136 | Check out the [getting a hold sample](https://github.com/PragTob/after_do/blob/master/samples/getting_a_hold.rb) for more. 137 | 138 | ### Attaching a callback to multiple methods 139 | 140 | In after_do you can attach a callback to multiple methods by just listing them: 141 | 142 | ```ruby 143 | SomeClass.after :one_method, :another_method do something end 144 | ``` 145 | 146 | Or you could pass in an Array of method names: 147 | 148 | ```ruby 149 | SomeClass.after [:one_method, :another_method] do something end 150 | ``` 151 | 152 | So for example if you have an activity and want the activity to be saved every time you change it, but you don't want to mix that persistence concern with what the activity actually does you could do something like this: 153 | 154 | ```ruby 155 | persistor = FilePersistor.new 156 | Activity.extend AfterDo 157 | Activity.after :start, :pause, :finish, :resurrect, 158 | :do_today, :do_another_day do |activity| 159 | persistor.save activity 160 | end 161 | ``` 162 | 163 | Doesn't that seem a lot drier then calling some save method manually after each of those in addition to separating the concerns? 164 | 165 | ### Attaching multiple callbacks to the same method 166 | 167 | A method can have as many callbacks as a Ruby Array can handle (although I do not recommend you to have many callbacks around). So this works perfectly fine: 168 | 169 | ```ruby 170 | MyClass.after :method do something end 171 | MyClass.after :method do another_thing end 172 | ``` 173 | 174 | The callbacks are executed in the order in which they were added. 175 | 176 | ### Working with inheritance 177 | 178 | after_do also works with inheritance. E.g. if you attach a callback to a method in a super class and that method is called in a sub class the callback is still executed. 179 | 180 | See this sample: 181 | 182 | ```ruby 183 | class A 184 | def a 185 | # ... 186 | end 187 | end 188 | 189 | class B < A 190 | end 191 | 192 | A.extend AfterDo 193 | A.after :a do puts 'a was called' end 194 | 195 | b = B.new 196 | b.a #prints out: a was called 197 | ``` 198 | 199 | ### Working with modules 200 | 201 | after_do works with modules just like it works with classes from version 0.3.0 onwards. E.g. you can just do: 202 | 203 | ```ruby 204 | class MyClass 205 | include MyModule 206 | # .... 207 | end 208 | 209 | MyModule.extend AfterDo 210 | MyModule.after :some_method do cool_stuff end 211 | 212 | MyClass.new.some_method # triggers callback 213 | ``` 214 | 215 | ### Working with module/class/singleton methods 216 | 217 | after_do also works with module/class/singleton methods whatever you want to call them, e.g. with `MyModule.method`, thanks to ruby's awesome object/class system. You just have to attach the callbacks to the singleton class of the object (because that's where the "class side" methods are defined). 218 | 219 | Take a look: 220 | 221 | ```ruby 222 | module M 223 | def self.magic 224 | puts 'magic' 225 | end 226 | end 227 | 228 | M.singleton_class.extend AfterDo 229 | M.singleton_class.after :magic do puts 'after_do is pure magic' end 230 | 231 | M.magic 232 | # Output is: 233 | magic 234 | after_do is pure magic 235 | ``` 236 | 237 | ### Usage from within a class 238 | 239 | If you got some repetitive tasks, that need to be done after/before a lot of methods in a class then you can also use after_do for this. This works a bit like `before_action`/`after_action` (or *_filter in the Rails 3 lingo) which you might know from Ruby on Rails. 240 | 241 | E.g. like this: 242 | 243 | ``` 244 | class MyClass 245 | extend AfterDo 246 | 247 | # ... 248 | 249 | after :my_method, :method2, :m3 do |args*, instance| instance.a_method end 250 | end 251 | ``` 252 | 253 | Check out the [within class sample](https://github.com/PragTob/after_do/blob/master/samples/within_class.rb) for a more complete example. 254 | 255 | 256 | ### Removing callbacks 257 | 258 | You can remove all callbacks you added to a class by doing: 259 | 260 | ```ruby 261 | MyClass.remove_all_callbacks 262 | ``` 263 | 264 | Note that this not remove callbacks defined in super/sub classes. 265 | 266 | ### Errors 267 | 268 | There are some custom errors that after_do throws. When you try to add a callback to a method which that class does not understand it will throw `AfterDo::NonExistingMethodError`. 269 | 270 | When an error occurs during one of the callbacks that are attached to a method it will throw `AfterDo::CallbackError` with information about the original error and where the block/callback causing this error was defined to help pinpoint the error. 271 | 272 | ### How does it work? 273 | 274 | When you attach a callback to a method with after_do what it basically does is it creates a copy of that method and then redefines the method to basically look like this (pseudo code): 275 | 276 | ```ruby 277 | execute_before_callbacks 278 | return_value = original_method 279 | execute_after_callbacks 280 | return_value 281 | ``` 282 | 283 | To do this some helper methods are defined in the AfterDo module. As classes have to extend the AfterDo module all the methods that you are not supposed to call yourself are prefixed with `_after_do_` to minimize the risk of method name clashes. The only not prefixed method are `after`, `before` and `remove_all_callbacks`. 284 | 285 | ### Method granularity 286 | 287 | after_do works on the granularity of methods. That means that you can only attach callbacks to methods. This is no problem however, since if it's your code you can always define new methods. E.g. if you want to attach callbacks to the end of some operation that happens in the middle of a method just define a new method for that piece of code. 288 | 289 | I sometimes do this for evaluating a block, as I want to do something when that block finished evaluating so I define a method `eval_block` wherein I just evaluate the block. 290 | 291 | ## Naming Clashes 292 | 293 | If other classes or modules define `after`, `before` or `remove_all_callbacks` methods you might run into problems. One of those examples is [ActionCable](https://github.com/PragTob/after_do/issues/22) and other classes that might interact with ActiveSupport's callback code. 294 | 295 | For this case AfterDo has an `AfterDo::AlternativeNaming` module that you can extend in AfterDos stead just like you would normally do. Then all these methods are prefixed with `ad_` to avoid naming clashes. 296 | 297 | ```ruby 298 | class WithClashes 299 | extend AfterDo::AlternativeNaming 300 | 301 | # ... 302 | 303 | ad_after :some_method do magic end 304 | end 305 | ``` 306 | 307 | Those names aren't the nices, now are they? Well, lucky you! You can easily define your own interface into `AfterDo` with your own method names as all methods live in `AfterDo::Core` and you just need to define which methods call them. For example: 308 | 309 | ```ruby 310 | module MyOwnAfterDo 311 | include AfterDo::Core 312 | 313 | def later(*methods, &block) 314 | _after_do_define_callback(:after, methods, block) 315 | end 316 | 317 | def earlier(*methods, &block) 318 | _after_do_define_callback(:before, methods, block) 319 | end 320 | 321 | def forget_it_all 322 | _after_do_remove_all_callbacks 323 | end 324 | end 325 | ``` 326 | 327 | Which you can then use as you'd expect, [see the sample for some more detail](https://github.com/PragTob/after_do/blob/master/samples/alternative_naming.rb) 328 | 329 | ## Is this a good idea? 330 | 331 | Always depends on what you are doing with it. As many things out there it has its use cases but can easily be misused. 332 | 333 | ### Advantages 334 | 335 | - Get cross cutting concerns packed together in one file - don't have them scattered all over your code base obfuscating what the real responsibility of that class is 336 | - Don't repeat yourself, define what is happening when in one file 337 | - I feel like it helps the Single Responsibility principle, as it enables classes to focus on what their main responsibility is and not deal with other stuff. I initially wrote this gem when I wanted to decouple an object of my domain from the way it is saved. 338 | 339 | ### Drawbacks 340 | 341 | - You lose clarity. With callbacks after a method it is not immediately visible what happens when a method is called as some behavior might be defined elsewhere. 342 | - You could use this to modify the behaviour of classes everywhere. Don't. Use it for what it is meant to be used for - a concern that is not the primary concern of the class you are adding the callback to but that class is still involved with. 343 | 344 | A use case I feel this is particularly made for is redrawing. That's what we use it for over at [shoes4](https://github.com/shoes/shoes4). E.g. we have multiple objects and different actions on these objects may trigger a redraw (such changing the position of a circle). This concern could be littered all over the code base. Or nicely packed into one file where you don't repeat yourself for similar redrawing scenarios and you see all the redraws at one glance. Furthermore it makes it easier to do things like "Just do one redraw every 1/30s" (not yet implemented). 345 | 346 | ## Does it work with Ruby interpreter X? 347 | 348 | Thanks to the awesome [travis CI](https://travis-ci.org/) the specs are run with CRuby 1.9.3, 2.0, 2.1, 2.2, 2.3, 2.4 and JRuby 1.7, 9.0 and 9.1. It _should_ work with rubinius, but some problems lead me to remove it from the build matrix. 349 | 350 | So in short, this should work with all of them and is aimed at doing so :-) 351 | 352 | ## Contributing 353 | 354 | Contributions are very welcome. Whether it's an issue or even a pull request. For pull requests you can use the following flow: 355 | 356 | 1. Fork it 357 | 2. Create your feature branch (`git checkout -b my-new-feature`) 358 | 3. Commit your changes (`git commit -am 'Add some feature'`) 359 | 4. Push to the branch (`git push origin my-new-feature`) 360 | 5. Create new Pull Request 361 | 362 | I'd also really appreciate spec only pull requests or bug reports with a failing spec/minimal example as this makes fixing it a lot easier =) 363 | 364 | Thanks in advance for all contributions of any kind! 365 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /after_do.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'after_do/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "after_do" 8 | spec.version = AfterDo::VERSION 9 | spec.authors = ["Tobias Pfeiffer"] 10 | spec.email = ["pragtob@gmail.com"] 11 | spec.description = %q{after_do is a gem that let's you execute a block of your choice after or before a specific method is called on a class. This is inspired by Aspect Oriented Programming and should be used to fight cross-cutting concerns.} 12 | spec.summary = %q{after_do allows you to add simple after/before hooks to methods} 13 | spec.homepage = "https://github.com/PragTob/after_do" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ["lib"] 19 | end 20 | -------------------------------------------------------------------------------- /lib/after_do.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The after_do library adds callbacks to methods in Ruby. Before using this 4 | # with any class, said class has to extend the AfterDo module first e.g. 5 | # MyClass.extend AfterDo 6 | # MyClass.after :some_method do awesome_stuff end 7 | # 8 | # More information at the github page: https://github.com/PragTob/after_do 9 | module AfterDo 10 | # The prefix for the copies of the original methods made by after_do 11 | ALIAS_PREFIX = '__after_do_orig_' 12 | 13 | # Raised when trying to attach a callback to a non existing method 14 | class NonExistingMethodError < StandardError ; end 15 | 16 | # Raised when an error occurs in one of the callbacks of a method. Provides 17 | # additional information such as which method and where the block was 18 | # defined. 19 | class CallbackError < StandardError ; end 20 | 21 | module Core 22 | private 23 | def _after_do_define_callback(type, methods, block) 24 | @_after_do_callbacks ||= _after_do_basic_hash 25 | methods = methods.flatten # in case someone used an Array 26 | _after_do_raise_no_method_specified(type) if methods.empty? 27 | methods.each do |method| 28 | _after_do_add_callback_to_method(type, method, block) 29 | end 30 | end 31 | 32 | def _after_do_raise_no_method_specified(type) 33 | raise ArgumentError, "#{type} takes at least one method name!" 34 | end 35 | 36 | def _after_do_basic_hash 37 | {after: _after_do_methods_hash, before: _after_do_methods_hash} 38 | end 39 | 40 | def _after_do_methods_hash 41 | Hash.new {|hash, key| hash[key] = []} 42 | end 43 | 44 | def _after_do_add_callback_to_method(type, method, block) 45 | alias_name = _after_do_aliased_name method 46 | _after_do_redefine(method, alias_name) unless _after_do_redefined?(alias_name) 47 | @_after_do_callbacks[type][method] << block 48 | end 49 | 50 | def _after_do_redefined?(alias_name) 51 | private_instance_methods(false).include? alias_name 52 | end 53 | 54 | def _after_do_redefine(method, alias_name) 55 | _after_do_raise_no_method_error(method) unless _after_do_defined?(method) 56 | _after_do_rename_old_method(method, alias_name) 57 | _after_do_redefine_method_with_callback(method, alias_name) 58 | end 59 | 60 | def _after_do_defined?(method) 61 | method_defined?(method) || private_method_defined?(method) 62 | end 63 | 64 | def _after_do_raise_no_method_error(method) 65 | raise NonExistingMethodError, "There is no method #{method} on #{self} to attach a block to with after_do" 66 | end 67 | 68 | def _after_do_aliased_name(symbol) 69 | (ALIAS_PREFIX + symbol.to_s).to_sym 70 | end 71 | 72 | def _after_do_rename_old_method(old_name, new_name) 73 | alias_method new_name, old_name 74 | private new_name 75 | end 76 | 77 | def _after_do_redefine_method_with_callback(method, alias_name) 78 | callback_klazz = self 79 | define_method method do |*args| 80 | callback_klazz.send(:_after_do_execute_before_callbacks, method, self, *args) 81 | return_value = send(alias_name, *args) 82 | callback_klazz.send(:_after_do_execute_after_callbacks, method, self, *args, return_value) 83 | return_value 84 | end 85 | end 86 | 87 | def _after_do_execute_before_callbacks(method, object, *args) 88 | _after_do_each_callback_wrapped(:before, method) do |callback| 89 | callback.call(*args, method, object) 90 | end 91 | end 92 | 93 | def _after_do_execute_after_callbacks(method, object, *args, return_value) 94 | _after_do_each_callback_wrapped(:after, method) do |callback| 95 | callback.call(*args, method, return_value, object) 96 | end 97 | end 98 | 99 | def _after_do_each_callback_wrapped(type, method) 100 | @_after_do_callbacks[type][method].each do |block| 101 | begin 102 | yield block 103 | rescue Exception => error 104 | raise CallbackError, "#{error.class}: #{error.message} raised during an after_do callback block for method '#{method}' on the instance #{self.inspect} defined in the file #{block.source_location[0]} in line #{block.source_location[1]}.\n This is the backtrace of the original error: \n #{error.backtrace.join("\n")}" 105 | end 106 | end 107 | end 108 | 109 | def _after_do_remove_all_callbacks 110 | @_after_do_callbacks = _after_do_basic_hash 111 | end 112 | end 113 | 114 | include Core 115 | # A method to add a callback to a method or a list of methods to be executed 116 | # after the original method was executed. E.g.: 117 | # MyClass.after :some_method do awesome_stuff end 118 | # It can only take a list of methods after which a block should be executed: 119 | # MyClass.after :method1, :method2, :method3 do puts 'jay!' end 120 | # The list can also be an Array. 121 | def after(*methods, &block) 122 | _after_do_define_callback(:after, methods, block) 123 | end 124 | 125 | # This method works much like .after - except blocks are executed 126 | # before the method is called. 127 | def before(*methods, &block) 128 | _after_do_define_callback(:before, methods, block) 129 | end 130 | 131 | # Removes all callbacks attached to methods in the module. 132 | def remove_all_callbacks 133 | _after_do_remove_all_callbacks 134 | end 135 | 136 | # Methods in here do exactly the same as their siblings above, with the minor 137 | # difference that their names are prefixed with `ad_` to avoid naming clashes. 138 | module AlternativeNaming 139 | include Core 140 | 141 | def ad_after(*methods, &block) 142 | _after_do_define_callback(:after, methods, block) 143 | end 144 | 145 | def ad_before(*methods, &block) 146 | _after_do_define_callback(:before, methods, block) 147 | end 148 | 149 | def ad_remove_all_callbacks 150 | _after_do_remove_all_callbacks 151 | end 152 | end 153 | end 154 | -------------------------------------------------------------------------------- /lib/after_do/version.rb: -------------------------------------------------------------------------------- 1 | module AfterDo 2 | # ::nodoc:: 3 | VERSION = '0.4.0' 4 | end 5 | -------------------------------------------------------------------------------- /samples/alternative_naming.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | class MyClashingClass 4 | extend AfterDo::AlternativeNaming 5 | 6 | def foo 7 | 42 8 | end 9 | 10 | def self.after 11 | nil 12 | end 13 | 14 | def self.before 15 | nil 16 | end 17 | end 18 | 19 | MyClashingClass.ad_after :foo do puts 'it works' end 20 | MyClashingClass.new.foo 21 | 22 | module MyOwnAfterDo 23 | include AfterDo::Core 24 | 25 | def later(*methods, &block) 26 | _after_do_define_callback(:after, methods, block) 27 | end 28 | 29 | def earlier(*methods, &block) 30 | _after_do_define_callback(:before, methods, block) 31 | end 32 | 33 | def forget_it_all 34 | _after_do_remove_all_callbacks 35 | end 36 | end 37 | 38 | class MyOtherClashingClass 39 | extend MyOwnAfterDo 40 | 41 | def foo 42 | 42 43 | end 44 | 45 | def self.after 46 | nil 47 | end 48 | 49 | def self.before 50 | nil 51 | end 52 | end 53 | 54 | MyOtherClashingClass.later :foo do puts 'my own works' end 55 | MyOtherClashingClass.earlier :foo do puts 'my own earlier works' end 56 | MyOtherClashingClass.new.foo 57 | -------------------------------------------------------------------------------- /samples/before.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | class MyClass 4 | attr_accessor :value 5 | end 6 | 7 | MyClass.extend AfterDo 8 | MyClass.after :value= do |*, obj| puts 'after: ' + obj.value.to_s end 9 | MyClass.before :value= do |*, obj| puts 'before: ' + obj.value.to_s end 10 | 11 | m = MyClass.new 12 | m.value = 'Hello' 13 | m.value = 'new value' 14 | 15 | # Output is: 16 | # before: 17 | # after: Hello 18 | # before: Hello 19 | # after: new value 20 | -------------------------------------------------------------------------------- /samples/dog.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | class Dog 4 | def bark 5 | puts 'Woooof' 6 | end 7 | end 8 | 9 | Dog.extend AfterDo 10 | Dog.after :bark do puts 'I just heard a dog bark!' end 11 | 12 | dog = Dog.new 13 | dog2 = Dog.new 14 | 15 | dog.bark 16 | dog2.bark 17 | -------------------------------------------------------------------------------- /samples/error_in_callback.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | class A 4 | def work 5 | # .. 6 | end 7 | end 8 | 9 | A.extend AfterDo 10 | A.after :work do 1/0 end 11 | 12 | A.new.work -------------------------------------------------------------------------------- /samples/getting_a_hold.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | class Example 4 | def zero 5 | 0 6 | end 7 | 8 | def two(a, b) 9 | a + b 10 | end 11 | 12 | def value 13 | 'some_value' 14 | end 15 | end 16 | 17 | Example.extend AfterDo 18 | 19 | Example.after :zero do puts 'Hello!' end 20 | 21 | # If the callback method takes no arguments then the first argument passed to 22 | # the block with will the method name, then the return value and finally the object itself 23 | Example.after :zero do |_name, _return, obj| puts obj.value end 24 | 25 | # The method arguments are passed to the callback as well: 26 | Example.after :two do |first, second| puts "#{first} #{second}" end 27 | 28 | # Now alltogether: 29 | Example.after :two do |a, b, name, value, obj| 30 | puts "after: #{a} #{b} #{name} #{value} #{obj.value}" 31 | end 32 | 33 | # Remember before can't get a return value as at that point the method hasn't 34 | # beene executed: 35 | Example.before :two do |a, b, name, obj| 36 | puts "before: #{a} #{b} #{name} #{obj.value}" 37 | end 38 | 39 | # You can also use the * to soak up all the method arguments and get 40 | # straight to the instance: 41 | Example.after :two do |*args, _name, _return, obj| 42 | puts "args passed to callback: #{args.join(', ')}" 43 | puts 'just ' + obj.value 44 | end 45 | 46 | e = Example.new 47 | e.zero 48 | e.two 1, 2 49 | # prints: 50 | # Hello! 51 | # some value 52 | # before: 1 2 two some_value 53 | # 1 2 54 | # after: 1 2 two 3 some_value 55 | # args passed to callback: 1, 2 56 | # just some value 57 | -------------------------------------------------------------------------------- /samples/inheritance.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | class A 4 | def a 5 | # ... 6 | end 7 | end 8 | 9 | class B < A 10 | end 11 | 12 | A.extend AfterDo 13 | A.after :a do puts 'a was called' end 14 | 15 | b = B.new 16 | b.a #prints out: a was called 17 | -------------------------------------------------------------------------------- /samples/singleton.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | module M 4 | def self.magic 5 | puts 'magic' 6 | end 7 | end 8 | 9 | M.singleton_class.extend AfterDo 10 | M.singleton_class.after :magic do puts 'after_do is pure magic' end 11 | 12 | M.magic 13 | M.magic 14 | 15 | # prints: 16 | # magic 17 | # after_do is pure magic 18 | # magic 19 | # after_do is pure magic -------------------------------------------------------------------------------- /samples/with_module.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | module M 4 | def method 5 | end 6 | end 7 | 8 | class A 9 | include M 10 | end 11 | 12 | class B 13 | include M 14 | end 15 | 16 | class C 17 | include M 18 | 19 | def method 20 | puts 'Overridden method' 21 | end 22 | end 23 | 24 | class D 25 | prepend M 26 | 27 | def method 28 | puts 'Wanna be Overriden method' 29 | end 30 | end 31 | 32 | M.extend AfterDo 33 | M.after :method do puts 'method called' end 34 | 35 | A.new.method 36 | B.new.method 37 | 38 | # won't call callback since the implementation was overriden 39 | C.new.method 40 | 41 | # will call callback since the module extending AfterDo was prepended 42 | D.new.method 43 | 44 | # Output is: 45 | # method called 46 | # method called 47 | # Overridden method 48 | # method called 49 | -------------------------------------------------------------------------------- /samples/within_class.rb: -------------------------------------------------------------------------------- 1 | require 'after_do' 2 | 3 | class Team 4 | extend AfterDo 5 | 6 | def add_member(member) 7 | # ... 8 | end 9 | 10 | def remove_member(member) 11 | # .. 12 | end 13 | 14 | def change_name(new_name) 15 | # .. 16 | end 17 | 18 | def save 19 | # .. 20 | puts 'saving...' 21 | end 22 | 23 | after :add_member, :remove_member, :change_name do |*, team| team.save end 24 | end 25 | 26 | team = Team.new 27 | team.add_member 'Maren' 28 | team.change_name 'Ruby Cherries' 29 | team.remove_member 'Guilia' 30 | 31 | # Output is: 32 | # saving... 33 | # saving... 34 | # saving... -------------------------------------------------------------------------------- /spec/after_do_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe AfterDo do 4 | 5 | shared_examples_for 'calling callbacks' do |ad_module, 6 | callback_adder, 7 | callback_remover| 8 | let(:dummy_instance) { dummy_class.new } 9 | let(:mockie) {double 'mock block', call: true} 10 | let(:dummy_class) do 11 | Class.new do 12 | extend ad_module 13 | def zero 14 | 0 15 | end 16 | 17 | def one(param) 18 | param 19 | end 20 | 21 | def two(param1, param2) 22 | param1 + param2 23 | end 24 | end 25 | end 26 | 27 | it 'does not monkey patch Class' do 28 | expect(Class.new).not_to respond_to callback_adder 29 | end 30 | 31 | it 'responds to before/after extended with AfterDo' do 32 | expect(dummy_class).to respond_to callback_adder 33 | end 34 | 35 | def simple_callback_called_test(callback_adder) 36 | dummy_class.send callback_adder, :zero do mockie.call_method end 37 | expect(mockie).to receive :call_method 38 | dummy_instance.zero 39 | end 40 | 41 | it 'calls a method on the injected mockie' do 42 | simple_callback_called_test callback_adder 43 | end 44 | 45 | it 'calls a method on the injected mockie even if that method is private' do 46 | dummy_class.send(:private, :zero) 47 | simple_callback_called_test callback_adder 48 | end 49 | 50 | it 'does not change the return value' do 51 | before_return_value = dummy_instance.zero 52 | dummy_class.send callback_adder, :zero do 42 end 53 | after_return_value = dummy_instance.zero 54 | expect(after_return_value).to eq before_return_value 55 | end 56 | 57 | it 'marks the copied method as private' do 58 | dummy_class.send callback_adder, :zero do end 59 | copied_method_name = (AfterDo::ALIAS_PREFIX + 'zero').to_sym 60 | expect(dummy_instance).not_to respond_to copied_method_name 61 | end 62 | 63 | it 'can add multiple call backs' do 64 | expect(mockie).to receive :call_method 65 | mock2 = double 66 | expect(mock2).to receive :call_another_method 67 | mock3 = double 68 | expect(mock3).to receive :bla 69 | dummy_class.send callback_adder, :zero do mockie.call_method end 70 | dummy_class.send callback_adder, :zero do mock2.call_another_method end 71 | dummy_class.send callback_adder, :zero do mock3.bla end 72 | dummy_instance.zero 73 | end 74 | 75 | describe 'removing callbacks' do 76 | it 'can remove all callbacks' do 77 | expect(mockie).not_to receive :call_method 78 | dummy_class.send callback_adder, :zero do mockie.call_method end 79 | dummy_class.send callback_remover 80 | dummy_instance.zero 81 | end 82 | 83 | it 'does not crash the addition of new callbacks afterwards' do 84 | expect(mockie).to receive :call_method 85 | dummy_class.send callback_adder, :zero do mockie.call_method end 86 | dummy_class.send callback_remover 87 | dummy_class.send callback_adder, :zero do mockie.call_method end 88 | dummy_instance.zero 89 | end 90 | end 91 | 92 | describe 'errors' do 93 | describe 'NonExistingMethodError' do 94 | it 'throws an error when you try to add a hook to a non existing method' do 95 | expect do 96 | dummy_class.send callback_adder, :non_existing_method do ; end 97 | end.to raise_error(AfterDo::NonExistingMethodError) 98 | end 99 | 100 | it 'does not throw the error for private methods (see #9)' do 101 | dummy_class.send(:private, :zero) 102 | expect do 103 | dummy_class.send callback_adder, :zero do ; end 104 | end.to_not raise_error 105 | end 106 | end 107 | 108 | describe 'errors in callbacks' do 109 | 110 | def expect_call_back_error(matcher = nil) 111 | expect do 112 | dummy_instance.zero 113 | end.to raise_error AfterDo::CallbackError, matcher 114 | end 115 | 116 | before :each do 117 | dummy_class.send callback_adder, :zero do raise StandardError, 'silly message' end 118 | end 119 | 120 | it 'raises a CallbackError' do 121 | expect_call_back_error 122 | end 123 | 124 | it 'mentions the error raised' do 125 | expect_call_back_error(/StandardError/) 126 | end 127 | 128 | it 'mentions the method called' do 129 | expect_call_back_error(/zero/) 130 | end 131 | 132 | it 'mentions the file the error was raised in' do 133 | expect_call_back_error Regexp.new __FILE__ 134 | end 135 | 136 | it 'mentions the original error message' do 137 | expect_call_back_error(/silly message/) 138 | end 139 | end 140 | end 141 | 142 | describe 'with parameters' do 143 | 144 | before :each do 145 | expect(mockie).to receive :call_method 146 | end 147 | 148 | it 'can handle methods with a parameter' do 149 | dummy_class.send callback_adder, :one do mockie.call_method end 150 | dummy_instance.one 5 151 | end 152 | 153 | it 'can handle methods with 2 parameters' do 154 | dummy_class.send callback_adder, :two do mockie.call_method end 155 | dummy_instance.two 5, 8 156 | end 157 | end 158 | 159 | describe 'with parameters for the given block' do 160 | it 'can handle one block parameter' do 161 | expect(mockie).to receive(:call_method).with(5) 162 | dummy_class.send callback_adder, :one do |i| mockie.call_method i end 163 | dummy_instance.one 5 164 | end 165 | 166 | it 'can handle two block parameters' do 167 | expect(mockie).to receive(:call_method).with(5, 8) 168 | dummy_class.send callback_adder, :two do |i, j| mockie.call_method i, j end 169 | dummy_instance.two 5, 8 170 | end 171 | end 172 | 173 | describe 'multiple methods' do 174 | def call_all_3_methods 175 | dummy_instance.zero 176 | dummy_instance.one 4 177 | dummy_instance.two 4, 5 178 | end 179 | 180 | it 'can take multiple method names as arguments' do 181 | expect(mockie).to receive(:call_method).exactly(3).times 182 | dummy_class.send callback_adder, :zero, :one, :two do 183 | mockie.call_method 184 | end 185 | call_all_3_methods 186 | end 187 | 188 | it 'can get the methods as an Array' do 189 | expect(mockie).to receive(:call_method).exactly(3).times 190 | dummy_class.send callback_adder, [:zero, :one, :two] do 191 | mockie.call_method 192 | end 193 | call_all_3_methods 194 | end 195 | 196 | it 'raises an error when no method is specified' do 197 | expect do 198 | dummy_class.send callback_adder do 199 | mockie.call_method 200 | end 201 | end.to raise_error ArgumentError 202 | end 203 | end 204 | 205 | describe 'it can get a hold of self, if needbe' do 206 | it 'works for a method without arguments' do 207 | expect(mockie).to receive(:call_method).with(dummy_instance) 208 | dummy_class.send callback_adder, :zero do |*_things, object| 209 | mockie.call_method(object) 210 | end 211 | dummy_instance.zero 212 | end 213 | 214 | it 'works for a method with 2 arguments' do 215 | expect(mockie).to receive(:call_method).with(1, 2, dummy_instance) 216 | dummy_class.send callback_adder, :two do |first, second, *_things, object| 217 | mockie.call_method(first, second, object) 218 | end 219 | dummy_instance.two 1, 2 220 | end 221 | end 222 | 223 | describe 'it can get a hold of the method name, if needbe' do 224 | it 'works for a method without arguments' do 225 | expect(mockie).to receive(:call_method).with(:zero) 226 | dummy_class.send callback_adder, :zero do |method_name| 227 | mockie.call_method(method_name) 228 | end 229 | dummy_instance.zero 230 | end 231 | 232 | it 'works for a method with arguments' do 233 | expect(mockie).to receive(:call_method).with(1, 2, :two) 234 | dummy_class.send callback_adder, :two do |arg1, arg2, method_name| 235 | mockie.call_method(arg1, arg2, method_name) 236 | end 237 | dummy_instance.two(1, 2) 238 | end 239 | 240 | it 'can also pass along the object' do 241 | expect(mockie).to receive(:call_method).with(1, 2, :two, dummy_instance) 242 | dummy_class.send callback_adder, :two do |arg1, arg2, method_name, *, inst| 243 | mockie.call_method(arg1, arg2, method_name, inst) 244 | end 245 | dummy_instance.two(1, 2) 246 | end 247 | end 248 | 249 | describe 'inheritance' do 250 | let(:inherited_instance) {inherited_class.new} 251 | let(:inherited_class) do 252 | Class.new dummy_class 253 | end 254 | 255 | it 'class knows about the after/before method' do 256 | expect(inherited_class).to respond_to callback_adder 257 | end 258 | 259 | describe 'callback on parent class' do 260 | before :each do 261 | dummy_class.send callback_adder, :zero do mockie.call end 262 | end 263 | 264 | it 'works when we have a callback on the parent class' do 265 | expect(mockie).to receive :call 266 | inherited_instance.zero 267 | end 268 | 269 | it 'remove_callbacks does not remove the callbacks on parent class' do 270 | expect(mockie).to receive :call 271 | inherited_class.send callback_remover 272 | inherited_instance.zero 273 | end 274 | 275 | it 'remove_callbacks on the parent does remove the callbacks' do 276 | expect(mockie).not_to receive :call 277 | dummy_class.send callback_remover 278 | inherited_instance.zero 279 | end 280 | end 281 | 282 | describe 'callback on child and parent class' do 283 | let(:overwriting_child_class) do 284 | Class.new dummy_class do 285 | def zero 286 | 0 287 | end 288 | end 289 | end 290 | 291 | let(:overwriting_child_instance) {overwriting_child_class.new} 292 | 293 | before :each do 294 | dummy_class.send callback_adder, :zero do mockie.call end 295 | overwriting_child_class.extend ad_module 296 | overwriting_child_class.send callback_adder, :zero do 297 | mockie.call 298 | end 299 | end 300 | 301 | it 'only calls the block once when calling a method on the child' do 302 | overwriting_child_instance.zero 303 | expect(mockie).to have_received :call 304 | end 305 | 306 | it 'only calls the block once when calling a method on the parent' do 307 | dummy_instance.zero 308 | expect(mockie).to have_received :call 309 | end 310 | end 311 | 312 | describe 'child class calling super' do 313 | let(:super_child_class) do 314 | Class.new dummy_class do 315 | def zero 316 | super 317 | end 318 | end 319 | end 320 | 321 | let (:super_child_instance) {super_child_class.new} 322 | 323 | before :each do 324 | dummy_class.send callback_adder, :zero do mockie.call end 325 | end 326 | 327 | it 'still calls the callback block from the parent class' do 328 | super_child_instance.zero 329 | expect(mockie).to have_received :call 330 | end 331 | end 332 | end 333 | 334 | describe 'included modules' do 335 | let(:dummy_module) do 336 | dummy = Module.new do 337 | def module_method 338 | 'module' 339 | end 340 | end 341 | dummy.extend ad_module 342 | dummy.send callback_adder, :module_method do mockie.call end 343 | dummy 344 | end 345 | 346 | let(:bare_class_with_module) do 347 | klazz = Class.new 348 | klazz.send(:include, dummy_module) 349 | klazz 350 | end 351 | 352 | 353 | let(:bare_instance_with_module) {bare_class_with_module.new} 354 | 355 | it 'executes callbacks from methods of included modules' do 356 | bare_instance_with_module.module_method 357 | expect(mockie).to have_received(:call) 358 | end 359 | 360 | describe '2 modules with the same method' do 361 | 362 | let(:other_dummy_module) do 363 | Module.new do 364 | def module_method 365 | 'other module' 366 | end 367 | end 368 | end 369 | 370 | before :each do 371 | other_module = other_dummy_module 372 | bare_class_with_module.send(:include, other_module) 373 | other_module.extend ad_module 374 | other_module.send callback_adder, :module_method do mockie.call end 375 | end 376 | 377 | it 'is still just called once (no super call)' do 378 | bare_instance_with_module.module_method 379 | expect(mockie).to have_received(:call) 380 | end 381 | 382 | end 383 | end 384 | 385 | describe 'module/class/singleton methods' do 386 | def singleton_module 387 | Module.new do 388 | def self.my_singleton_method 389 | 'singleton_method' 390 | end 391 | end 392 | end 393 | 394 | it 'works when you use the singleton_class' do 395 | my_module = singleton_module 396 | my_module.singleton_class.extend ad_module 397 | my_module.singleton_class.send callback_adder, :my_singleton_method do 398 | mockie.call 399 | end 400 | my_module.my_singleton_method 401 | expect(mockie).to have_received(:call) 402 | end 403 | 404 | end 405 | end 406 | 407 | context 'normal name clash free setup' do 408 | it_behaves_like 'calling callbacks', AfterDo, :after, :remove_all_callbacks 409 | it_behaves_like 'calling callbacks', AfterDo, :before, :remove_all_callbacks 410 | 411 | describe 'before and after behaviour' do 412 | let(:dummy_instance) { dummy_class.new } 413 | let(:mockie) {double 'mock block', call: true} 414 | let(:dummy_class) do 415 | Class.new do 416 | extend AfterDo 417 | def zero 418 | 0 419 | end 420 | 421 | def one(param) 422 | param 423 | end 424 | 425 | def two(param1, param2) 426 | param1 + param2 427 | end 428 | end 429 | end 430 | 431 | let(:callback){double 'callback', before_call: nil, after_call: nil} 432 | 433 | before :each do 434 | dummy_class.before :zero do callback.before_call end 435 | dummy_class.after :zero do callback.after_call end 436 | end 437 | 438 | it 'calls the before callback' do 439 | expect(callback).to receive :before_call 440 | dummy_instance.zero 441 | end 442 | 443 | it 'calls the after callback' do 444 | expect(callback).to receive :after_call 445 | dummy_instance.zero 446 | end 447 | 448 | it 'receives the calls in the right order' do 449 | expect(callback).to receive(:before_call).ordered 450 | expect(callback).to receive(:after_call).ordered 451 | dummy_instance.zero 452 | end 453 | 454 | describe 'difference between before and after' do 455 | it 'after also has access to the return value' do 456 | expect(mockie).to receive(:call).with(10, 20, dummy_instance, :two, 30) 457 | dummy_class.after :two do |*args, name, value, inst| 458 | mockie.call(args.first, args.last, inst, name, value) 459 | end 460 | dummy_instance.two 10, 20 461 | end 462 | 463 | it 'before can not access to the return value' do 464 | expect(mockie).to receive(:call).with(10, 20, dummy_instance, :two) 465 | dummy_class.before :two do |*args, name, inst| 466 | mockie.call(args.first, args.last, inst, name) 467 | end 468 | dummy_instance.two 10, 20 469 | end 470 | end 471 | end 472 | end 473 | 474 | context 'setup with name clashes' do 475 | let(:dummy_instance) { dummy_class.new } 476 | let(:mockie) {double 'mock block', call: true} 477 | let(:dummy_class) do 478 | Class.new do 479 | extend AfterDo 480 | def zero 481 | 0 482 | end 483 | 484 | def one(param) 485 | param 486 | end 487 | 488 | def two(param1, param2) 489 | param1 + param2 490 | end 491 | 492 | def self.after(_) 493 | 'foo' 494 | end 495 | 496 | def self.before(_) 497 | 'bar' 498 | end 499 | end 500 | end 501 | 502 | it 'sadly doesn\'t work with normal before/after' do 503 | dummy_class.after :zero do mockie.call end 504 | dummy_instance.zero 505 | expect(mockie).not_to have_received(:call) 506 | end 507 | 508 | describe 'circumvent it using alternative naming' do 509 | it_behaves_like 'calling callbacks', 510 | AfterDo::AlternativeNaming, 511 | :ad_after, 512 | :ad_remove_all_callbacks 513 | it_behaves_like 'calling callbacks', 514 | AfterDo::AlternativeNaming, 515 | :ad_before, 516 | :ad_remove_all_callbacks 517 | end 518 | end 519 | end 520 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'simplecov' 2 | 3 | SimpleCov.start do 4 | add_filter '/spec/' 5 | end 6 | 7 | require 'after_do' 8 | --------------------------------------------------------------------------------