rspec/rspec-expectations — pull request #332 by myronmarston (2013-10-04 07:07:29+00:00)
Title: Rewrite custom matcher DSL

Rewrite custom matcher DSL.

Rather than evaling the `define` block in the
context of the matcher instance, eval the `define`
block in the context of the matcher instance's
singleton class.

* Fixes https://github.com/rspec/rspec-expectations/pull/272.
`include` in `define` has a different meaning (module inclusion)
than `include` in the `match` block (using the `include` matcher to
match).
* Better solution than https://github.com/rspec/rspec-expectations/pull/194
for https://github.com/rspec/rspec-expectations/issues/188. There's now
a `match` class method and a `match` instance method.
* Completely avoids issues we had to use hacks to solve before:
https://github.com/rspec/rspec-expectations/pull/29,
https://github.com/rspec/rspec-expectations/pull/38,
https://github.com/rspec/rspec-expectations/commit/fc4b66dc16b0394ca9d6f63a93296b035052c485

This also adds a few misc improvements:

* Each custom matcher gets its own class, which is assigned to a constant in the `RSpec::Matchers::Custom` namespace.
* I split the large number of methods up into a few modules for greater clarity about what is what: compare `Macros` vs `DefaultImplementations`.
* All user overrides created by the DSL methods now compile to an actual method, so that users can use early returns, such as:

``` ruby
RSpec::Matchers.define :foo_bar do |expected|
match do |actual|
return false if actual == :unexpected
actual.compares_to?(expected)
end
end
```
* There were some places where things were underspecified; I added specs for them.
* On 1.9+, `respond_to_missing?` is defined so that `matcher.method(:some_example_method)` works.
* Improved the docs a bit.
* `chain` blocks can now accept blocks.

There is one breaking change that we'll probably want to put warnings in 2.99 about. In a case like this:

``` ruby
RSpec::Matchers.define :foo_bar do |expected|
extend HelperModuleThatDefinesFoo
def self.bar; end

match do |actual|
bar == foo
end
end
```

...it would work previously, because `self` in the `define` block was the same as `self` within `match` (and the other DSL blocks). Now, `self` in the `define` block is the singleton class, so it has the feel of defining a class, with the blocks having instance-level scope. That means that methods from extended modules or defined using `self.` syntax cannot be called from `match` as they could before.

I _think_ this is 100% compatible with what we had before otherwise.

Feedback wanted!

/cc @dchelimsky @xaviershay @JonRowe