Callbacks are executable things, like a Proc
or methods in Ruby, whereas a hook is usually a spot in your code where you want to execute a certain subset of callbacks.
Or, precisely, callbacks that are associated to the hook.
I have lots of these places throughout my libraries, and after playing around with ActiveSupport::Callbacks
(both 2.3 and 3.0) I wrote a minimal gem to do exactly that:
Declaratively define hooks, add callbacks and run them with the options you like.
Let’s see how it works. First, install hooks.
$ gem install hooks
Using hooks
Usually you’d define hooks declaratively in your class.
class Coder include Hooks define_hook :after_work
You’re now ready to add callbacks, as your class now has a Class.after_work
method. Nothing more.
class Coder after_work do puts "Yeah!" end
Instance methods are also callback’able.
after_work :have_a_beer def have_a_beer puts "One for me, one for Michał!" end
Now, run a hook somewhere in your code.
def finish cleanup! run_hook :after_work end
The callbacks will be invoked in the order you added them. Sweet.
Running a hook with arguments
If you need objects in your callbacks, just pass ’em.
run_hook :at_work, self, Time.now
And be sure to equip your callbacks with the right parameters.
at_work do |who, when| ... end
Filters?
So, basically a filter as found in Rails controllers is a hook. You – the user – use Controller.after_login
to add callbacks. And somewhere in the Rails core, there’d be a call to
run_hook :after_login, self
…if they’d be using hooks.
Thanks for making this dead simple! Hopefully this will lower the bar to make libraries more extensible!
LikeLike
Nice stuff! Improves semantics and understanding, though it feels a bit clumsy.
LikeLike
It’s nice to see projects like this. I’ve been thinking about looking into building hooks-like gem, but I’m so glad you did the work for me!
Thanks.
LikeLike
Is ability to pass params the only thing that is better than is ActiveSupport callbacks?
LikeLike
Hey Andrius,
from the functional point of view, passing params is the only advantage. ActiveSupport::Callbacks is even “better” as it allows more types of callbacks (strings that are evaluated and more bullshit).
However, the AS module is extremely complex, full of method compilations and is overally hard to read, to maintain and to understand. It’s getting even worse in the 3.0 version.
Hooks by contrast is stupid simple and will stay stupid simple.
LikeLike
You are absolutely right about the extreme complexity of callbacks in rails 3. Over there reside some behemot-methods made of evaluated strings.
Greg Pollack says that the heavy use of method compilation particularly in the callbacks, was chosen for better performance when compared with method_missing.
Anyhow, it is difficult to me to believe that it could be significantly faster than define_method at the singleton class level, in addition to be significantly less readable. Jay Field wrote a really interesting piece about the “meta_def” method.
I was really intrigued by this topic, and when I found a question on stackoverflow, I tried to answer with a simple implementation of the pattern:
http://stackoverflow.com/questions/1677861/how-to-implement-a-callback-in-ruby/3638771#3638771
So I am really pleased that you made this gem, I am stunned by the simplicity of the code, especially if compared with its functionality.
Don’t get me wrong, Rails is a damned piece of genius’ work, but your work demonstrates that maybe one day its internals will be again more approachable for the non-guru guys.
LikeLike
@LucaB your’re totally right. Rails core team seems to get rid of clear code for micro speed improvements. It’s ridiculous, especially in ruby world.
@Andirus hooks technique is way more powerful than callbacks, but less convenient. In most cases hooks are not called automatically like callbacks. I would say than Nick has created something between closures and callbacks
LikeLike
@LucaB: Good point! Heavy usage of method compilation as well as method_missing is WRONG. There is no excuse for unmaintainable code, even the performance argument is ridiculous. I mean, we are talking about “nano-seconds” here 😉
So, I prefer a good architecture and simple code in exchange for having to buy a faster server. Or a cloud. Or more RAM. Or whatever.
LikeLike
We’re featured in a TV show here (min 28:20) – check out the show, it’s quite amusing.
LikeLike