rspec/rspec-core — pull request #1749 by myronmarston (2014-10-28 22:13:28+00:00)
Title: [WIP] Apply module/shared context inclusion to individual examples

**This is an idea I'm toying with. I lean towards including it but I'm not totally sold on the idea yet.**

### The problem

We have two different mechanisms for defining metadata-filtered shared context. On the configuration object:

``` ruby
RSpec.configure do |config|
config.around(:example, :db_1) { |ex| DB.transaction(:always_rollback => true, &ex) }
end
```

...and as a shared context:

``` ruby
RSpec.shared_context "with a DB transaction", :db_2 do
around(:example) { |ex| DB.transaction(:always_rollback => true, &ex) }
end
```

The latter is generally more useful because `shared_context` supports all rspec-core constructs, including `let`, helper methods, etc...where as the config object only provides the means to define hooks that way. So I'd consider the latter form to be generally "better" but it has a downside:

``` ruby
RSpec.describe "Group with both approaches applied", :db_1, :db_2 do
# examples
end

RSpec.describe "Group with an example that has both approaches applied" do
it "does stuff", :db_1, :db_2 do
end
end
```

Both forms work fine in the first example (where the metadata has been applied to the group), but only the config-based approach (`:db_1`) works in the second example. The problem is that the `shared_context` is included (or not) in example groups, not individual examples, whereas the config can apply just fine. A user might reasonably expect the 2nd example to work, but as things currently stand, it won't. I myself have run into this a few times, where I had something that I had defined as a `shared_context` but thought I had defined on `config`, and I tagged an example and it didn't work.

### Solution

I realized recently that ruby's object model (particularly the `singleton_class`) provides a means to very simply address this issue: we can include modules/shared contexts directly in the singleton class of the example group instance owned by an individual example. This allows the contents of the module or shared context to apply to one example, but not others in the group. I'm pleased with how simple the implementation was.

However, I'm not 100% sold on this, for a few reasons:

* It's potentially surprising behavior for people that have a good mental model of example groups vs examples...those users probably wouldn't expect this to work.
* It feels a little bit "magical" (even though in practice, it's actually really simple and is just leveraging ruby's object model).
* It adds some additional processing to each example when it runs, potentially making RSpec a bit slower.
* Including a module in an object's singleton class at run time busts the method cache, potentially having an even bigger affect on perf. (However, every time rspec-mocks features are used, the method cache gets busted since it has to do similar things all the time).
* There's some slightly odd semantics with `before(:context)`/`after(:context)` hooks that are defined in the shared context. As currently implemented, when the context is applied to one example, these do not run. However, they potentially setup and teardown some state that other parts of the context (such as an `around` hook) use, so not running them could cause problems. I'm on the fence about whether or not they should be run.
* This could be a breaking change for some folks. Although, I would not consider it a SemVer violation.
* It's potentially confusing that `config.extend` modules aren't treated the same way. I didn't think it made sense to apply those modules the same, since they generally define class-level helper methods (e.g. macros) that are called from within a `describe` or `context` block. (But can anyone thing of a need or use to treat them the same way?)

### TODO:

- [ ] Get the build to pass on all rubies (I want to discuss this before putting effort into that).
- [ ] Benchmark the perf difference this has.
- [ ] Decide how to handle `:context` hooks.
- [ ] Figure out how this should be documented.

Feedback wanted.

/cc @rspec/rspec