How to Set a Hook to Run Code at the End of a Ruby Class Definition

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



Leave a reply



Submit