RCapture is a Ruby library that allows placing hooks on methods using a convenient interface. In this article I’d like to introduce RCapture and its features to you.

Update RCapture is covered in episode #41 of Ruby5. Take a listen!

I always wanted to be able to place hooks on arbitrary methods in Ruby. After two failed attempts to bring up a easy to use and consistent interface, I put this idea aside. I kept the idea in my mind since then, but it took me roughly a year to start a new approach from a totally different point of view based on the controversial discussed article “Ruby Metaprogramming Simplified“.



RCapture offers the following core features

Capturing of instance and class methods of individual objects or entire population of objects.

Capturing pre or post method invocation.

Multiple capturings per method.

Modify method arguments and return values.

Filter method calls.

Developed with multithreaded environments in mind.

RCapture has been tested against Ruby 1.8 and Ruby 1.9 and can be easily installed by

> gem install rcapture Successfully installed rcapture-1.0.4

Introductory Example

Here is a simple example that demonstrates usage of RCapture: Array insertion methods are captured in order to output statistics upon invocation. The capture is placed at class Array which will affect all instances of Array.

require 'rcapture' class Array # RCapture::Interceptable is a module mixin that provides capturing capatibilities. include RCapture::Interceptable end Array.capture_post :methods => [:<<, :push] do |cs| puts "#{cs.args.first} was inserted to array #{cs.sender}" end [] << 1 << 2 [].push 3 #=> 1 was inserted to array [1] #=> 2 was inserted to array [1, 2] #=> 3 was inserted to array [3]

Placing hooks on individual instances is accomplished by calling capture_post on instance level:

require 'rcapture' x = [] x.extend(RCapture::Interceptable) x.capture_post :methods => [:<<, :push] do |cs| puts "#{cs.args.first} was inserted to x" end x << 1 << 2

Class Methods

Similar to placing hooks on instance methods, class methods can be captured by RCapture. Here is a working example.

require 'rcapture' # Enrich Math module module Math include RCapture::Interceptable end Math.capture_pre :class_methods => [:cos, :acos, :sin, :asin] do puts "Hello Trigonometry!" end Math.cos(0) #=> Hello Trigonometry!

Modifying Arguments and Return Values

The following working example illustrates capturing methods in order to modify input and return arguments. Captures are placed multiple times.

require 'rcapture' # Enrich single instance x = [] x.extend(RCapture::Interceptable) # Define procs that will modify the input arguments inc = Proc.new { |ci| ci.args[0] += 1 } dec = Proc.new { |ci| ci.args[0] -= 1 } mul = Proc.new { |ci| ci.args[0] *= 2 } # Capture ':<<' multiple times. x.capture_pre :methods => :<<, &inc x.capture_pre :methods => :<<, &mul x.capture_pre :methods => :<<, &inc x.capture_pre :methods => :<<, &dec x.capture_pre :methods => :<<, &dec x << 2 << 4 p x #=> [3,7] # Similarily, you can modify return values y = [] y.extend(RCapture::Interceptable) inc = Proc.new { |ci| ci.return += 1 } dec = Proc.new { |ci| ci.return -= 1 } mul = Proc.new { |ci| ci.return *= 2 } y.capture_post :methods => :[], &inc y.capture_post :methods => :[], &mul y.capture_post :methods => :[], &inc y.capture_post :methods => :[], &dec y.capture_post :methods => :[], &dec y << 1 << 4 p y[0] #=> 3 p y[1] #=> 9#

Filtering Method Calls

As noted at the beginning of the post, RCapture is capable of filtering method calls using capture_pre . The following example, creates an array that will accept event numbers only.

require 'rcapture' # This array will only accept even numbers x = [] x.extend(RCapture::Interceptable) even_filter = Proc.new do |ci| # Define the predicate that must evaluate to true # in order to call the captured method ci.predicate = (ci.args.first % 2 == 0) # In case the predicate evaluates to false you # can use the return property to control # what is returned from the captured method instead # Insertion to array returns the array itself: ci.return = ci.sender end x.capture_pre :methods => [:<<, :push], &even_filter x << 2 << 3 << 4 << 5 << 6 x.push(7).push(8) p x #=> [2,4,6,8]#

Final Example: New with Block argument

Here is one final example that should stimulate your imagination of what can be done with RCapture: given any class, it is often desireable to work with newly created instance by passing a block to New . If that class does not support this you can use RCapture to enable this behaviour.

require 'rcapture' include RCapture class X include Interceptable def initialize(name); @name = name; end def say_hello; puts "Hello, #{@name}!"; end end class Y < X def initialize; super("Y"); end end X.capture :class_methods => :new do |ci| ci.block.call(ci.return) if ci.block end # Now you can use X and Y as if it supports # blocks x = X.new("Christoph") do |x| x.say_hello #=> "Hello, Christoph!" end y = Y.new do |y| y.say_hello #=> "Hello, Y!" end # Or leaf the block argument away x = X.new("Christoph") y = Y.new#

Even More!

More examples and documentation can be found as part of the RCapture distribution or by browsing the online documentation.

Help Wanted

Due to the limited amount of time I can spend on this project, I’m actively searching for people joining the project. If you’d like to contribute, get in touch now!

Links

RCapture Gem – Gem is hosted by Gemcutter

RCapture Sourcecode

RCapture Documentation – The mighty documentation