How can I set a hook to run code at the end of a Ruby class definition?
Use TracePoint
to track when your class sends up an :end
event.
General solution
This module will let you create a self.finalize
callback in any class.
module Finalize
def self.extended(obj)
TracePoint.trace(:end) do |t|
if obj == t.self
obj.finalize
t.disable
end
end
end
end
Now you can extend your class and define self.finalize
, which will run as soon as the class definition ends:
class Foo
puts "Top of class"
extend Finalize
def self.finalize
puts "Finalizing #{self}"
end
puts "Bottom of class"
end
puts "Outside class"
# output:
# Top of class
# Bottom of class
# Finalizing Foo
# Outside class
Specific solution to OP's problem
Here's how you can fit TracePoint
directly into your pre-existing module.
require 'active_support/all'
module MyPlugin
extend ActiveSupport::Concern
module ClassMethods
def consumes_my_plugin(**options)
m = options[:specific_method_to_use]
TracePoint.trace(:end) do |t|
break unless self == t.self
raise ArgumentError.new("#{m} is not defined") unless instance_methods.include?(m)
t.disable
end
end
end
end
The examples below demonstrate that it works as specified:
# `def` before `consumes`: evaluates without errors
class MethodBeforePlugin
include MyPlugin
def your_method; end
consumes_my_plugin option1: :value1, specific_method_to_use: :your_method
end
# `consumes` before `def`: evaluates without errors
class PluginBeforeMethod
include MyPlugin
consumes_my_plugin option1: :value1, specific_method_to_use: :your_method
def your_method; end
end
# `consumes` with no `def`: throws ArgumentError at load time
class PluginWithoutMethod
include MyPlugin
consumes_my_plugin option1: :value1, specific_method_to_use: :your_method
end
Defining method_called.. How do I make a hook method which gets called every time any function of a class gets called?
Take a look at Kernel#set_trace_func
. It lets you specify a proc which is invoked whenever an event (such as a method call) occurs. Here's an example:
class Base
def a
puts "in method a"
end
def b
puts "in method b"
end
end
set_trace_func proc { |event, file, line, id, binding, classname|
# only interested in events of type 'call' (Ruby method calls)
# see the docs for set_trace_func for other supported event types
puts "#{classname} #{id} called" if event == 'call'
}
b = Base.new
b.a
b.b
Outputs:
Base a called
in method a
Base b called
in method b
Running code after class is fully loaded
In Ruby a class is never fully loaded. You can reopen it whenever you want to.
class A
def method_in_a
…
end
end
You can do later, no matter where your code is (even in another source code file).
class A
alias :aliased_method_in_a :method_in_a
end
or you could do it the way you wrote it (which is exactly the same as the previous code)
A.class_eval do
alias :aliased_method_in_a :method_in_a
end
As you pointed out A#method_in_a must exist at the time you alias it. To ensure this is true you could do
require "file_of_class_a"
before you do the alias. If you do not know when the method A#method_in_a will be created you could do
class A
def self.method_added(name)
alias :aliased_method_in_a :method_in_a if name == :method_in_a
end
end
A.method_added gets automatically called whenever a method in A gets defined.
Is there a way to add hooks to a Thor class in order to run code before/after all commands?
Looks like there's no built-in way of doing such callbacks in the Thor gem. (I'd think this would be a popular feature, but maybe I'm looking at things the wrong way.)
An above comment points to a third-party solution, thor-hollaback, but I have not tried it.
Execute code at end of Module/Class, like Ruby test/unit
Test::Unit
uses at_exit for this, which runs code immediately before your application quits:
at_exit do
puts "printed before quit"
end
.. other stuff
I don't think there is any way to run code specifically after a class or module definition is closed.
Ruby: How to hook onto class methods
You need to include the FollowingHook code on Class
and then call following so that it applies to the class methods.
Class.send(:include, FollowingHook)
class User
class << self
following :get do |reciever, args|
# Your awesome code here
end
end
end
Edit:
Here is my complete working solution which followed this suggestion:
# Contains methods to hook method calls
module FollowingHook
module ClassMethods
private
# Hook the provided instance methods so that the block
# is executed directly after the specified methods have
# been invoked.
#
def following(*syms, &block)
syms.each do |sym| # For each symbol
str_id = "__#{sym}__hooked__"
unless private_instance_methods.include?(str_id)
alias_method str_id, sym # Backup original
# method
private str_id # Make backup private
define_method sym do |*args| # Replace method
ret = __send__ str_id, *args # Invoke backup
block.call(self, # Invoke hook
:method => sym,
:args => args,
:return => ret
)
ret # Forward return value of method
end
end
end
end
end
def self.included(base)
base.send(:extend, FollowingHook::ClassMethods)
end
end
class User
def self.foo
puts "foo"
end
def bar
puts "bar"
end
end
# You can put this in the class << self block if you prefer that the
# methods only be available on the User class.
Class.send(:include, FollowingHook)
class User
include FollowingHook
following :bar do |receiver, args|
puts receiver.inspect
end
class << self
# If you prefer only the User class include FollowingHooks, use the
# following instead of including them in Class.
# include FollowingHook
following :foo do |receiver, args|
puts receiver.inspect
end
end
end
User.foo #=>
# foo
# User
User.new.bar #=>
# bar
# #<User:0x338d9d>
How do I run code before and after a method in a sub class?
I'd play with alias_method
:
module Timeable
def time_methods *meths
meths.each do |meth|
alias_method "old_#{meth}", meth
define_method meth do |*args|
started_at = Time.now
res = send "old_#{meth}", *args
puts "Execution took %f seconds" % (Time.now - started_at)
res
end
end
end
end
class Foo
def bar str
puts str
end
end
Foo.extend Timeable
Foo.time_methods :bar
Foo.new.bar('asd')
#=>asd
#=>Execution took 0.000050 seconds
call before methods in model on ruby
You can do this with prepend
. prepend
is like include
in that it adds a module to the ancestors of the class, however instead of adding it after the class it adds it before.
This means that if a method exists both in the prepended module and the class then the module implementation is called first (and it can optionally call super
if it wants to call the base class).
This allows you to write a hooks module like so:
module Hooks
def before(*method_names)
to_prepend = Module.new do
method_names.each do |name|
define_method(name) do |*args, &block|
puts "before #{name}"
super(*args,&block)
end
end
end
prepend to_prepend
end
end
class Example
extend Hooks
before :foo, :bar
def foo
puts "in foo"
end
def bar
puts "in bar"
end
end
In real use you would probably want to stash that module somewhere so that each call to before
doesn't create a new module but that is just an inplementation detail
Related Topics
How to Print Stdout Immediately
Cannot Assign Requested Address - Bind(2) (Errno::Eaddrnotavail)
MAC Osx Lion and Ruby - [Fatal] Failed to Allocate Memory
Sinatra Does Not Start with Twitter Gem
Ignoring a Character Along with Word Boundary in Regex
Error Loading Active Record Gem with Sinatra App Using Rvm
Check Method Call on Model Using Minitest
How to Get the Array Index or Iteration Number with an Each Iterator
Save All Image Files from a Website
Can One Yaml Object Refer to Another
Calendar Events Notification Not Received on Server
How to Find Current Abstract Route in Rails Middware