Hooks
When trying to ensure that something happens around some kind of event it’s usually best if you work with a hook designed for that scenario rather than manually implementing one. This becomes most evident in JavaScript events with jQuery and Bootstrap effects. If you write an event listener for some change around these effects without a hook you might run into problems. Hooks are basically your inside man letting you know and use opportunity when it arises.
Lets say you want to have all plugins that inherit the Plugin structure to register themselves in the StellarApplication module. If you put it in initialize for the Plugin module then all inheriting classes can call super to initialize it.
require 'set' module StellarApplication class << self def plugins @plugins ||= Set.new end def register_plugin(plugin) plugins << plugin end end class Plugin def initialize StellarApplication.register_plugin( self.class ) end end end class DoTheEpic < StellarApplication::Plugin def initialize # ... do stuff ... super # ... do stuff ... end end a = DoTheEpic.new StellarApplication.plugins # => #<Set: {DoTheEpic}>
There are good and bad things about this. The first flaw that becomes apparent is that the plugin will call the register_plugin command every time an instance of the plugin is created DoTheEpic.new . The good thing though is that Set keeps a unique set of items so you won’t end up with duplicate class names in your “list” of them. Another issue that may occur to you is “What if they forget to call super?”. Then the plugin will fail to register for the application. So the first thing we want to do is have the code executed at class inheritance and not at instance instantiation. For that we have hooks.
require 'set' module StellarApplication class << self def plugins @plugins ||= Set.new end def register_plugin(plugin) plugins << plugin end end module Plugin def self.included(base) StellarApplication.register_plugin( base ) end end end class DoTheFlop include StellarApplication::Plugin end StellarApplication.plugins # => #<Set: {DoTheFlop}>
In Ruby the included method will be ran when another Object calls include on the module to which it resides. The code above is shorter, simpler, only calls the register command once per plugin, and you don’t have to worry about some one forgetting to call super in their initialize method. base will be the class itself that called include.
Hooks in Ruby each get passed a parameter for which they were designed.
- included( Class that called with include )
- extended( Class that called with extend )
- inherited( Class that was inherited with < )
- prepended( Class that called with prepend )
- method_added( Method symbol of new method created )
- method_removed( Method symbol of method removed )
- method_undefined( Method symbol of undefined method )
- singleton_method_added( Method symbol of new method created )
- singleton_method_removed( Method symbol of method removed )
- singleton_method_undefined( Method symbol of undefined method )
Other hooks that don’t take parameters the same way as these include at_exit, method_missing, and set_trace_func. at_exit takes a block which should be run when your application is exiting. method_missing takes whatever you want it to handle and generally should have an args parameter. set_trace_func needs a Proc which will handle these parameters: event, file, line, id, binding, classname.
Hooks give you a wonderful way to leverage change in most any situation. Whenever there is an event that creates change having a hook to use comes in most handy. For Rails hooks are called Callbacks: ActiveRecord::Callbacks, and in Bootstrap they’re called Events. So whatever change you want to hook into you should get familiar with what’s been provided in the tool(s).
Summary
Hooks are fairly new to me as I’ve only used them over the past year. If you’d like to see a plugin system I implemented before I knew about hooks I wrote three different educational versions in this repository Pluggable Module Meta Controller back in 2012. The way I wrapped my mind around implementing it was to envision the old telephone system where you called an operator and she/he would ask how they may connect your call. Having hooks available in a language definitely makes plugins a lot easier!
Know of any other hooks and tips about them? I’d love to hear about it! Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!
God Bless!
-Daniel P. Clark
Image by Andrew Stawarz via the Creative Commons Attribution-NoDerivs 2.0 Generic License.