{"id":13880259,"url":"https://github.com/PragTob/after_do","last_synced_at":"2025-07-16T16:31:28.305Z","repository":{"id":9437166,"uuid":"11312473","full_name":"PragTob/after_do","owner":"PragTob","description":"after_do allows you to add simple callbacks to methods","archived":false,"fork":false,"pushed_at":"2020-07-05T09:27:14.000Z","size":102,"stargazers_count":113,"open_issues_count":4,"forks_count":7,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-07-06T10:53:59.234Z","etag":null,"topics":["aspect-oriented-programming","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PragTob.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2013-07-10T13:19:15.000Z","updated_at":"2025-01-30T18:06:51.000Z","dependencies_parsed_at":"2022-09-10T14:20:10.308Z","dependency_job_id":null,"html_url":"https://github.com/PragTob/after_do","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/PragTob/after_do","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fafter_do","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fafter_do/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fafter_do/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fafter_do/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PragTob","download_url":"https://codeload.github.com/PragTob/after_do/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PragTob%2Fafter_do/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265524632,"owners_count":23782016,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspect-oriented-programming","ruby"],"created_at":"2024-08-06T08:02:53.986Z","updated_at":"2025-07-16T16:31:27.985Z","avatar_url":"https://github.com/PragTob.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# 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)\n\nafter_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.\n\nIf the class extends `AfterDo` you can simply do this by\n\n```\nMyClass.after :some_method do whatever_you_want end\n# you can also do before\nMyClass.before :a_method do so_much end\n```\n\nSome facts about after_do:\n\n* no runtime dependencies\n* 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\n* simple DSL\n* no monkey patching\n\n## Why would you want to do this?\nWell to fight cross-cutting concerns. These are concerns in an applications that apply to multiple objects (e.g. they cross-cut).\nA 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.\nWith after_do you could put all the logging in one file. Other use cases include gathering business statistics or redrawing timing of elements.\nThis should generally not be done to alter behavior of the class and its instances - this makes programs more confusing rather than easier to understand.\n\nThe 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...)\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n    gem 'after_do'\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install after_do\n\nAnd then you have to require the gem before you can use it:\n\n    require 'after_do'\n\n## Usage\n\nThis 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.\n\n### General usage\n\nIn 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`.\nWith this setup you can add a callback to `method` like this:\n\n```ruby\nMyClass.after :method do magic end\n```\n\nWith after_do you can do simple things like printing something out every time a method is called as in this example:\n\n```ruby\nclass Dog\n  def bark\n    puts 'Woooof'\n  end\nend\n\nDog.extend AfterDo\nDog.after :bark do puts 'I just heard a dog bark!' end\n\ndog = Dog.new\ndog2 = Dog.new\n\ndog.bark\ndog2.bark\n\n# Output is:\n# Woooof\n# I just heard a dog bark!\n# Woooof\n# I just heard a dog bark!\n```\n\n### Getting a hold of the method arguments, the object and more!\n\nWith 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.\n\nSo if you have a method that takes two arguments you can get those like this:\n\n```ruby\nMyClass.after :two_arg_method do |argument_one, argument_2| something end\n```\n\nThe method name is passed in as the third argument:\n\n```ruby\nMyClass.before :two_arg_method do |argument_one, argument_2, method_name|\n  something\nend\n```\n\nThe 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.\n\nAnother argument that is passed in, but only for `after` is the return value of the method so you can do:\n\n```ruby\nMyClass.after :two_arg_method do |arg1, arg2, name, return_value|\n  report return_value\nend\n```\n\nThe object itself is passed in as the last block argument finally so all arguments together are:\n\n```ruby\nMyClass.after :method do |arg1, arg2, name, return_value, object|\n  magic\nend\n\n# remember before doesn't have the return value\nMyClass.before :method do |arg1, arg2, name, object|\n  other_magic\nend\n```\n\nWhy 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.\n\nOf 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:\n\n```ruby\nMyClass.after :two_arg_method do |*, obj| fancy_stuff(obj) end\n```\n\n\nIf 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 :-)\n\nCheck out the [getting a hold sample](https://github.com/PragTob/after_do/blob/master/samples/getting_a_hold.rb) for more.\n\n### Attaching a callback to multiple methods\n\nIn after_do you can attach a callback to multiple methods by just listing them:\n\n```ruby\nSomeClass.after :one_method, :another_method do something end\n```\n\nOr you could pass in an Array of method names:\n\n```ruby\nSomeClass.after [:one_method, :another_method] do something end\n```\n\nSo 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:\n\n```ruby\npersistor  = FilePersistor.new\nActivity.extend AfterDo\nActivity.after :start, :pause, :finish, :resurrect,\n             :do_today, :do_another_day do |activity|\n  persistor.save activity\nend\n```\n\nDoesn't that seem a lot drier then calling some save method manually after each of those in addition to separating the concerns?\n\n### Attaching multiple callbacks to the same method\n\nA 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:\n\n```ruby\nMyClass.after :method do something end\nMyClass.after :method do another_thing end\n```\n\nThe callbacks are executed in the order in which they were added.\n\n### Working with inheritance\n\nafter_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.\n\nSee this sample:\n\n```ruby\nclass A\n  def a\n    # ...\n  end\nend\n\nclass B \u003c A\nend\n\nA.extend AfterDo\nA.after :a do puts 'a was called' end\n\nb = B.new\nb.a #prints out: a was called\n```\n\n### Working with modules\n\nafter_do works with modules just like it works with classes from version 0.3.0 onwards. E.g. you can just do:\n\n```ruby\nclass MyClass\n  include MyModule\n  # ....\nend\n\nMyModule.extend AfterDo\nMyModule.after :some_method do cool_stuff end\n\nMyClass.new.some_method # triggers callback\n```\n\n### Working with module/class/singleton methods\n\nafter_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).\n\nTake a look:\n\n```ruby\nmodule M\n  def self.magic\n    puts 'magic'\n  end\nend\n\nM.singleton_class.extend AfterDo\nM.singleton_class.after :magic do puts 'after_do is pure magic' end\n\nM.magic\n# Output is:\nmagic\nafter_do is pure magic\n```\n\n### Usage from within a class\n\nIf 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.\n\nE.g. like this:\n\n```\nclass MyClass\n  extend AfterDo\n\n  # ...\n\n  after :my_method, :method2, :m3 do |args*, instance| instance.a_method end\nend\n```\n\nCheck out the [within class sample](https://github.com/PragTob/after_do/blob/master/samples/within_class.rb) for a more complete example.\n\n\n### Removing callbacks\n\nYou can remove all callbacks you added to a class by doing:\n\n```ruby\nMyClass.remove_all_callbacks\n```\n\nNote that this not remove callbacks defined in super/sub classes.\n\n### Errors\n\nThere 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`.\n\nWhen 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.\n\n### How does it work?\n\nWhen 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):\n\n```ruby\nexecute_before_callbacks\nreturn_value = original_method\nexecute_after_callbacks\nreturn_value\n```\n\nTo 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`.\n\n### Method granularity\n\nafter_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.\n\nI 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.\n\n## Naming Clashes\n\nIf 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.\n\nFor 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.\n\n```ruby\nclass WithClashes\n  extend AfterDo::AlternativeNaming\n  \n  # ...\n  \n  ad_after :some_method do magic end\nend\n```\n\nThose 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:\n\n```ruby\nmodule MyOwnAfterDo\n  include AfterDo::Core\n\n  def later(*methods, \u0026block)\n    _after_do_define_callback(:after, methods, block)\n  end\n\n  def earlier(*methods, \u0026block)\n    _after_do_define_callback(:before, methods, block)\n  end\n\n  def forget_it_all\n    _after_do_remove_all_callbacks\n  end\nend\n```\n\nWhich 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)\n\n## Is this a good idea?\n\nAlways depends on what you are doing with it. As many things out there it has its use cases but can easily be misused.\n\n### Advantages\n\n- 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\n- Don't repeat yourself, define what is happening when in one file\n- 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.\n\n### Drawbacks\n\n- 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.\n- 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.\n\nA 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).\n\n## Does it work with Ruby interpreter X?\n\nThanks 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.\n\nSo in short, this should work with all of them and is aimed at doing so :-)\n\n## Contributing\n\nContributions are very welcome. Whether it's an issue or even a pull request. For pull requests you can use the following flow:\n\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\nI'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 =)\n\nThanks in advance for all contributions of any kind!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FPragTob%2Fafter_do","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FPragTob%2Fafter_do","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FPragTob%2Fafter_do/lists"}