method_missing magic - emulating Groovy's "it" in Ruby
2 Oct, 2006
Inspired variously by:
- Symbol#to_proc
- Nat Pryce's articles on Higher-Order Messaging in Ruby
- Groovy's implied "it" closure-parameter
I've cooked up a shortcut for generating simple blocks, meaning that rather than
people.select { |x| x.name.length > 10 }
I can write such things as:
people.select(&its.name.length > 10)
Disclaimer: I think this is more "cool hack" than useful tool; it's probably too much of an alien artifact to be useful in real life. And it's not generally applicable, like "it" in Groovy. And really, it's not that much more verbose to use a block. Aaaaaanyway ...
The trick is that the above is parsed as
people.select(&(its.name.length.>(10)))
The "its" method creates a MessageBuffer object, which records the messages (method invocations) sent it's way:
irb(main):001:0> require 'message_buffer' => true irb(main):002:0> its => #<MessageBuffer:0x6b40b44 @messages=[]> irb(main):003:0> its.name.length < 10 => #<MessageBuffer:0x6b3e678 @messages=[[:name], [:length], [:<, 10]]>
Now, the "&" operator coerces it's argument to a Proc, and MessageBuffer#to_proc generates a Proc that replays all the recorded messages. Q.E.D.
The full source-code is fairly short, so I'll include it inline:
class MessageBuffer
instance_methods.each do |m|
undef_method m unless m =~ /^(__|respond_to|inspect)/
end
def initialize
@messages = []
end
def method_missing(*message)
@messages << message # record the message
self # return self so we can keep recording
end
def __replay_all_messages__(obj)
@messages.inject(obj) do |obj, message|
obj.__send__(*message)
end
end
def to_proc
proc { |x| __replay_all_messages__(x) }
end
end
def its
MessageBuffer.new
end
Update: Florian Gross suggested a better way to replay recorded
messages, using inject, and I've updated the code accordingly.






Feedback