Skip to content

Instantly share code, notes, and snippets.

@ryanlecompte
Created October 13, 2011 04:50
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 13 You must be signed in to fork a gist
  • Save ryanlecompte/1283413 to your computer and use it in GitHub Desktop.
Save ryanlecompte/1283413 to your computer and use it in GitHub Desktop.
Providing an ActiveRecord-like before_filter capability to arbitrary Ruby classes
# 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)
@chuckbergeron
Copy link

What I was looking for. Right on. Thanks!

@josal
Copy link

josal commented May 25, 2014

It doesn't work here with ruby 2

undefined method before_hook for Foo:Class (NoMethodError)

@erick8911
Copy link

It works with ruby 2, maybe you missed the last line to include in all classes

Class.send(:include, ExecutionHooks)

Or for a single class just

extend ExecutionHooks

In the class file

@brenoperucchi
Copy link

@chiperific
Copy link

Thanks for this!
Any suggestions on passing a variable to the before_hook?

e.g.: before_hook :refresh_user_token(user)

@foton
Copy link

foton commented Jul 11, 2018

Thanks a lot, it helps me track of execution order of methods call. Just modified line 72 to method(hook).call(method_name) and set before_hook : print_executed_action

@fguillen
Copy link

fguillen commented May 17, 2019

Thanks.. very inspiring

I have created a version for a simple around_action like functionality:

@surabhisuman
Copy link

Thanks for this solution.
Any idea on how can we extend this before_hook for Superclass as well? This before_hook works when hook is added to a method of any class and method is called directly but doesn't work when the hook is added to a method of its superclass.
Thanks in advance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment