-
-
Save reddyonrails/5709993 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# First the end result of what we want: | |
class Foo | |
before_hook :whoa | |
before_hook :amazing | |
def test | |
puts "This is kinda cool!" | |
end | |
def amazing | |
puts "Pretty amazing!" | |
end | |
def whoa | |
puts "Whoa!" | |
end | |
end | |
Foo.new.test | |
# OUTPUT BELOW | |
Whoa! | |
Pretty amazing! | |
This is kinda cool! | |
# And this is how I implemented it: | |
# Demonstrates how to provide ActiveRecord-like before_filter | |
# hooks for arbitrary classes. | |
module ExecutionHooks | |
# this method is invoked whenever a new instance method is added to a class | |
def method_added(method_name) | |
# do nothing if the method that was added was an actual hook method, or | |
# if it already had hooks added to it | |
return if hooks.include?(method_name) || hooked_methods.include?(method_name) | |
add_hooks_to(method_name) | |
end | |
# this is the DSL method that classes use to add before hooks | |
def before_hook(method_name) | |
hooks << method_name | |
end | |
# keeps track of all before hooks | |
def hooks | |
@hooks ||= [] | |
end | |
private | |
# keeps track of all currently hooked methods | |
def hooked_methods | |
@hooked_methods ||= [] | |
end | |
def add_hooks_to(method_name) | |
# add this method to known hook mappings to avoid infinite | |
# recursion when we redefine the method below | |
hooked_methods << method_name | |
# grab the original method definition | |
original_method = instance_method(method_name) | |
# re-define the method, but notice how we reference the original | |
# method definition | |
define_method(method_name) do |*args, &block| | |
# invoke the hook methods | |
self.class.hooks.each do |hook| | |
method(hook).call | |
end | |
# now invoke the original method | |
original_method.bind(self).call(*args, &block) | |
end | |
end | |
end | |
# Make it available to all classes. | |
Class.send(:include, ExecutionHooks) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment