Calling a method in Ruby can be thought of as sending a message to an object. This isn't just an abstract concept, it's actually implemented in the language by the send method.
'foo'.length
# => 3
'foo'.send :length
# => 3
This is a really important method in Ruby (often used in meta-programming), so important in fact, that just in case you accidentally overwrite send in your own code, it is aliased as __send__ to make sure it can always be called.
One aspect of send that often causes discussion1 is the fact that it allows you to call private methods which would otherwise be inaccessible using the regular method calling syntax, for example:
class Foo
private
def bar
'baz'
end
end
foo = Foo.new
foo.bar
# => NoMethodError: private method `bar' called for #<Foo:0x118fcbc>
foo.send :bar
# => "baz"
And for a while, Ruby 1.9 changed the implementation of send to only allow public methods to be called, introducing a new send! method2 which continued to allow private methods to be called. However, this has now been removed from Ruby 1.9 in favour of keeping the functionality of send as is. Instead, a new public_send method has been introduced which as you might expect will only call public methods.
Writing custom RSpec expectation matchers is a great way to enhance the readability of your specifications. As an example, here's a simple matcher to check whether a collection contains any of the given items. An example will make things clearer:
One thing to note is that include_any expects multiple arguments rather than a collection, hence the splat. Here's the code:
module Spec
module Matchers
def include_any(*args)
IncludeAny.new(*args)
end
class IncludeAny
def initialize(*args)
@args = args
end
def matches?(target)
@target = target
@target.include_any?(*@args)
end
def failure_message
"expected #{@target.inspect} to include one or more elements from #{@args.inspect}"
end
def negative_failure_message
"expected #{@target.inspect} not to include any elements from #{@args.inspect}"
end
end
end
end
This is about as trivial as a custom matcher gets. All of the work is done by the include_any? method, which doesn't actually exist as part of the core Ruby API. In fact it's a simple extension to Enumerable that I think I originally borrowed from Merb. Put the following in config/initializers/core_extensions.rb or similar to use it in a Rails app.
module Enumerable
def include_any?(*args)
args.any? { |arg| self.include?(arg) }
end
end