Introducing RCapture

Placing Hooks with RCapture

Placing Hooks with RCapture

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

About these ads

7 thoughts on “Introducing RCapture

  1. 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

  2. Pingback: Caffeine Driven Development » Blog Archive » L33t Links #67

    • 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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s