We actually make use of the fact that with Ruby you can add a
method_missing
method to any class that allows you to capture calls to any method that are ... well missing. Here's a simple example that shows how it works.# Create a class with the method_missing method. If a method
# is called on this class and it's not found, then this method
# will be called. We'll then print whatever was passed in along
# with the fact that it's not there.
class MM
def x
puts "Called x"
end
def y
puts "Called y"
end
def method_missing(name, *args, &block)
puts "You tried to call #{name} with #{args.inspect}. There is no method with that name."
end
end
if __FILE__ == $PROGRAM_NAME
# Create a new MM
mm = MM.new
# Call the two mehtods that do exist.
mm.x
mm.y
# Call z (which doesn't exist) both with and without
# paramaters.
mm.z
mm.z 1, 2, 3
end
This code creates a simple class called MM with three methods,
x
, y
, and method_missing
. The first two only print out the fact that they were called and the third will print out any other method that gets called, say z 1,2, 3
and will print out the name and parameters that get passed in to it. The main code just creates a new MM
and calls a few methods (existing and not) on it.Admittedly, this isn't too awfully interesting, but in our next example, we'll actually use
method_missing
to save ourselves some work. Here's the second bit of code:# Create a class MM1. The x and y methods require calling
# setup before and teardown after. This means that everytime
# we have to add a new method (say z()), we end up having to
# duplicate this code.
class MM1
def setup
puts "Setup"
end
def teardown
puts "Tear down"
end
def x
setup
puts "Called x"
teardown
end
def y
setup
puts "Called y"
teardown
end
end
# Create a class with the method_missing method. Here we
# still require x and y to have setup and teardown called, but
# we wrap it in method_missing as setup_x_teardown or setup_y_teardown.
# This allows us to also add z without having to worry about remembering
# the setup/teardown.
class MM2
def setup
puts "Setup"
end
def teardown
puts "Tear down"
end
# The method_missing() does all of the work here. We check if the
# name is setup_something_teardown and if it is, we call setup,
# call something with the parameters passed in, and then call teardown.
# If the method isn't of this form, we just pass it along.
def method_missing(name, *args, &block)
case (name)
when /^setup_(.*)_teardown/
setup
send($1, *args, & block)
teardown
else
super
end
end
def x
puts "Called x"
end
def y
puts "Called y"
end
end
if __FILE__ == $PROGRAM_NAME
# Create a new MM1
mm1 = MM1.new
# Call the two mehtods that do exist.
mm1.x
mm1.y
# Create a new MM2
mm2 = MM2.new
mm2.setup_x_teardown
mm2.setup_y_teardown
end
There's two classes here that both "do" the same thing. The first class
MM1
has two methods, x
and y
that require a setup
before they are called and a teardown
after they are called.The second class,
MM2
, has the same requirements, but we're going to handle them a bit differently. We have the same setup
and teardown
, but we don't have x
and y
call them directly. Instead we use method_missing
to handle the setup
and teardown
. What we're going to do is use method_missing
and then check the name parameter. If it matches something that looks like setup_X_teardown
, then we'll call setup
, call the method using send
, and finally, call teardown
. If we don't match the pattern, then we just pass the call up the chain. The main code here creates both an MM1
and an MM2
and shows a bit of how to use them.Here, in this simple example, you probably wouldn't bother with this. In the book Brown gives a much better use case, but this should give you some ideas at least.
Let me know if you have questions or comments.
Generally for things like your setup / teardown example, I prefer a block interface:
ReplyDeleteclass Foo
def process
setup
yield
teardown
end
# ...
end
obj = Foo.new
obj.process { obj.do_something }
Thanks for mentioning RBP!