Skip to content

Instantly share code, notes, and snippets.

@fffx
Forked from ryanlecompte/gist:1283413
Created October 25, 2018 03:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fffx/0beee2d5dc8142bff1670b15dedaccc9 to your computer and use it in GitHub Desktop.
Save fffx/0beee2d5dc8142bff1670b15dedaccc9 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment