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

Nice!
This makes some things easier, but I think people should at least take a look at what makes this metaprogramming stuff work.
Anyway, keep up the good job.
Thanks for your feedback geo! I agree with you, but have to note that the rcapture documentation is currently user-doc and not dev-doc which could make understanding rcpature internals harder.
- Christoph
Pingback: Caffeine Driven Development » Blog Archive » L33t Links #67
Totally cool! Nice work :)
Thanks :)
I’m glad people use it!
- Christoph
interesting and useful.
I’m wondering why you’re not hosting the code on github (too) ?
Elise,
thanks for your feedback. Are you using RCapture on a regular basis?
As far as github is concerned, I feel more comfortable with centralized version control systems such as subversion. However, I accept that branching/forking projects is common practice in the open source world and is better accomplished using a decentralized version control system such as git.
Frankly, my time quota for maintaining RCapture is marginal. Fortunately, it seems that the current revision is stable and virtually bug-free and does not require to much of attention.
If you like you can join the project and/or provide a git access. Drop me a note if you are interested.
– Christoph