Evaluating shared example customisation block before shared block

Hi

I finally looked into why this is not currently possibly in RSpec 2
(beta 19):

shared_examples_for “Etymology” do
describe “The etymology of foo” do
it “is followed by #{after_foo}” do
# …
end
end
end

describe “foo”, focus: true do
it_should_behave_like “Etymology” do
def self.after_foo
“bar”
end
end
end

It’s because of the current implementation of
ExampleGroup.define_shared_group_method, which evaluates the shared
example block before the customisation block:

shared_group = describe(“#{report_label} #{name}”, &shared_block)
shared_group.class_eval(&customization_block) if customization_block

(This is behaviour I found surprising.)

However, with a little more metaprogramming jiggery-pokery, you can have
them evaluated in the other order:

module RSpec
module Core
class ExampleGroup
# …

    def self.define_shared_group_method(new_name, report_label=nil)
      report_label = "it should behave like" unless report_label
      module_eval(<<-END_RUBY, __FILE__, __LINE__)
        def self.#{new_name}(name, &customization_block)
          shared_block = world.shared_example_groups[name]
          raise "Could not find shared example group named 

#{name.inspect}" unless shared_block

          compound_block = lambda do |*args|
            module_eval &customization_block if customization_block
            module_eval &shared_block
          end

          shared_group = describe("#{report_label} \#{name}", 

&compound_block)
shared_group
end
END_RUBY
end

    # ...
  end
end

end

Would this be a useful improvement to RSpec 2? Any opinions on the
order of the block evaluation for shared examples?

Cheers
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Jul 30, 2010, at 5:13 AM, Ashley M. wrote:

end

   # ...
           module_eval &shared_block

end
end

Or …

  def self.define_shared_group_method(new_name, report_label=nil)
    report_label = "it should behave like" unless report_label
    module_eval(<<-END_RUBY, __FILE__, __LINE__)
      def self.#{new_name}(name, &customization_block)
        shared_block = world.shared_example_groups[name]
        raise "Could not find shared example group named 

#{name.inspect}" unless shared_block

        describe "#{report_label} \#{name}" do
          module_eval &customization_block if customization_block
          module_eval &shared_block
        end
      end
    END_RUBY
  end

Would this be a useful improvement to RSpec 2?

Yes

Any opinions on the order of the block evaluation for shared examples

Makes perfect sense to me. Wanna make a patch with an additional
scenario in the cuke?

On Jul 30, 2010, at 9:03 AM, David C. wrote:

  # ...

end
module RSpec

   module_eval(<<-END_RUBY, __FILE__, __LINE__)
 end

Would this be a useful improvement to RSpec 2?

Yes

Any opinions on the order of the block evaluation for shared examples

Makes perfect sense to me. Wanna make a patch with an additional scenario in the cuke?

Actually - maybe I spoke to soon. Ordering things this way would mean
that you couldn’t do this:

shared_examples_for Enumerable do
def enumerable
raise “you must provide an enumerable method that returns the object
which you’re specifying should behave like Enumerable”
end
it “…” { … }
end

Although, if you were going to do that, I guess you could do this:

shared_examples_for Enumerable do
unless defined?(:enumerable)
raise “you must provide an enumerable method that returns the object
which you’re specifying should behave like Enumerable”
end
it “…” { … }
end

Maybe that, or a DSL that wraps that, is the better way, so we can get
the best of both worlds?

shared_examples_for Enumerable do
require_instance_method :foo, “gotta have foo instance method”
require_class_method :foo, “gotta have foo class method”
require_instance_variable “@foo”, “gotta have an instance variable
named @foo
it “…” { … }
end

Thoughts?

On Jul 30, 2010, at 3:10 pm, David C. wrote:

Maybe that, or a DSL that wraps that, is the better way, so we can get the best of both worlds?

shared_examples_for Enumerable do
require_instance_method :foo, “gotta have foo instance method”
require_class_method :foo, “gotta have foo class method”
require_instance_variable “@foo”, “gotta have an instance variable named @foo
it “…” { … }
end

Thoughts?

Actually, I much prefer this, as it makes it explicit what the host
spec must provide in order to have the shared behaviour. (Wincent’s
email has just popped up so consider this a response to his message
too.) I lean towards making things fail as early and explicitly as
possible, as it reduces the total worldwide developer head-scratching
time. InvalidSharedExampleUsageError(“Must provide instance method
:foo”) or some such wins for me over NoMethodError(“undefined method foo
on <#Class …>”). There should be no requirement to use this DSL
though, so the key would be to make sure it doesn’t muddy the RSpec
code.

This is probably something that could be solved in code quicker than
debate. It’s a yak I’m happy to shave before getting back to my own
project as it impacts what I’m working on … want me to have a look at
it this evening, David? I can fork rspec-core and play around with the
idea.

Regards
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Jul 30, 2010, at 10:57 AM, Ashley M. wrote:

end

Thoughts?

Actually, I much prefer this, as it makes it explicit what the host spec must provide in order to have the shared behaviour. (Wincent’s email has just popped up so consider this a response to his message too.) I lean towards making things fail as early and explicitly as possible, as it reduces the total worldwide developer head-scratching time. InvalidSharedExampleUsageError(“Must provide instance method :foo”) or some such wins for me over NoMethodError(“undefined method foo on <#Class …>”). There should be no requirement to use this DSL though, so the key would be to make sure it doesn’t muddy the RSpec code.

This is probably something that could be solved in code quicker than debate. It’s a yak I’m happy to shave before getting back to my own project as it impacts what I’m working on … want me to have a look at it this evening, David? I can fork rspec-core and play around with the idea.

By all means.

El 30/07/2010, a las 16:10, David C.
escribió:

require_instance_method :foo, “gotta have foo instance method”
require_class_method :foo, “gotta have foo class method”
require_instance_variable “@foo”, “gotta have an instance variable named @foo
it “…” { … }
end

Thoughts?

I think the DSL would probably be over-engineering.

One of the purposes the DSL would fulfill is to alert the user if he/she
forgets to provide some required support methods or variables, but you
already get those alerts “for free” in the form of failing specs and
NoMethodErrors etc, so I don’t think that really justifies it.

The other purpose of the DSL would be to explicitly list the
“dependencies” of the shared example group to someone who’s scanning it.
Again, I’m not sure if it’s really justified, given that a much simpler
solution already exists:

shared_examples_for Enumerable do
# requires:
# - instance method: foo
# - class method: foo
# - instance variable: @foo

it "..." { ... }

end

(I’m a big fan of doing the simplest thing that could possibly work.)

Cheers,
Wincent

On Jul 30, 2010, at 4:47 pm, David C. wrote:

If I provide spec extensions for a library (other than RSpec in this case) that include a shared group that “you can use to spec your foos to make sure they comply with this API” (or whatever), then this sort of thing can be very helpful. Make sense?

That’s exactly what I want to do with the framework/library code I’m
(slowly) extracting =)


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Jul 30, 2010, at 10:17 AM, Wincent C. wrote:

Although, if you were going to do that, I guess you could do this:
shared_examples_for Enumerable do
One of the purposes the DSL would fulfill is to alert the user if he/she forgets to provide some required support methods or variables, but you already get those alerts “for free” in the form of failing specs and NoMethodErrors etc, so I don’t think that really justifies it.
end

(I’m a big fan of doing the simplest thing that could possibly work.)

That would only work if people read the docs :slight_smile:

The programatic approach would warn the user when they try to do
something.

I’m not sold on the DSL at this point, but I do like the idea of having
a library help the developer do the right thing. If I provide spec
extensions for a library (other than RSpec in this case) that include a
shared group that “you can use to spec your foos to make sure they
comply with this API” (or whatever), then this sort of thing can be very
helpful. Make sense?

On 30 Jul 2010, at 5:00 PM, David C. wrote:

By all means.

I’ve started on that and filed a ticket[1].

One question I have, is I keep calling the Example Group that uses a
shared block the “host group”. Is there already a name for it? I never
know what to use, and I’m not sure “host group” describes it accurately
anyway.

Cheers
Ash

[1] Shared example configuration block should run before the example groups · Issue #99 · rspec/rspec-core · GitHub


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Jul 30, 2010, at 6:56 PM, Myron M. wrote:

Cheers
Ash

[1]Shared example configuration block should run before the example groups · Issue #99 · rspec/rspec-core · GitHub

http://www.patchspace.co.uk/http://www.linkedin.com/in/ashleymoran

I may be the only one who finds this useful, but I think there’s value
in evaluating the customization block after the shared example group
block. It allows the shared example group to provide a default
implementation of a helper method, and then an instance of the shared
behavior to override the helper method if appropriate. If you
evaluate the customization block before the shared example group
block, the default implementation wins out, and you have no way to
override helper methods in an instance of a shared example group.

You can still get the same outcome, but you have to implement it in the
group like this:

unless defined?(:foo)
def foo; “foo”; end
end

I think it’s a good trade-off to put that burden on the group (and it’s
author) rather that the consumers of the group.

I may be the only one who finds this useful, but I think there’s value
in evaluating the customization block after the shared example group
block. It allows the shared example group to provide a default
implementation of a helper method, and then an instance of the shared
behavior to override the helper method if appropriate. If you
evaluate the customization block before the shared example group
block, the default implementation wins out, and you have no way to
override helper methods in an instance of a shared example group.

On Jul 30, 2:58 pm, Ashley M. [email protected]

You can still get the same outcome, but you have to implement it in the group like this:

unless defined?(:foo)
def foo; “foo”; end
end

Good point–I hadn’t thought of that. The one issue I see with it is
that the author of the shared example group may not have knowledge of
which helper methods consumers will need to override. So he/she
either defines all helper methods that way, or guesses about which
ones to define that way (and potentially guesses wrong).

Maybe a DSL method while I’m working on it? Maybe:

default_helper(:foo) do
“foo”
end

WDYT?

If we go the route of having the customization block evaluated first,
then I like the idea, but I’m generally wary of adding more DSL
methods to RSpec. I think we should be careful to only add new DSL
methods that many people will find useful. If you find it useful,
it’s very easy to use it in your project without it being part of
RSpec: just define default_helper in a module, and use config.extend
YourModule in the RSpec.configuration block. (Note that I’m not
against adding this to RSpec: I just want to be sure we don’t add a
bunch of DSL methods that have limited usefulness.)

Looking back at the initial example that prompted the thread, it looks
to me like the primary use case for evaluating the customization block
first is so that you can parameterize the shared example group’s
example descriptions. (There may be other use cases for defining a
class-level helper methods, but none springs to mind). I also do this
frequently. Often times I have something like this:

[:foo, :bar, :baz].each do |method|
it “does something for #{method}” do
subject.send(method).should …
end
end

In this case I’m using the method parameter at the class level (to
interpolate into the description string) and at the instance level
(within the example itself).

If we evaluated the customization block first, it would allow this,
but you’d have to define both an instance and class helper:

it_should_behave_like “something” do
def self.method_name; :foo; end
def method_name; :foo; end
end

I think this is a clunky way to essentially pass a parameter to the
shared example group. Better would be something like this:

it_should_behave_like “something” do
providing :method_name, :foo
end

The instance of the shared example group provides :foo as the value of
the method_name parameter. providing simply defines a class and an
instance helper method with the given value.

I’ve written up an untested gist with a start for the code that would
implement this:

I think there’s value in evaluating the customization block first and
value in evaluating it last. We can get the best of both worlds if we
limit what’s evaluated first to a subset (say, a few DSL methods, and
maybe all class method definitions), extract it, and evaluate that
first, then evaluate the shared block first, then evaluate the
customization block. The gist demonstrates this as well. This may
confuse people, but it does give us the best of both worlds, I think.

Myron

On Jul 31, 12:56 am, Ashley M. [email protected]

On 31 Jul 2010, at 1:10 AM, David C. wrote:

You can still get the same outcome, but you have to implement it in the group like this:

unless defined?(:foo)
def foo; “foo”; end
end

Maybe a DSL method while I’m working on it? Maybe:

default_helper(:foo) do
“foo”
end

WDYT?

I think it’s a good trade-off to put that burden on the group (and it’s author) rather that the consumers of the group.

Agreed, it’d cause a lot of duplication of effort the other way round.


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Jul 31, 2010, at 7:06 pm, Myron M. wrote:

I think this is a clunky way to essentially pass a parameter to the
shared example group. Better would be something like this:

it_should_behave_like “something” do
providing :method_name, :foo
end

After sleeping on this, I found an elegant solution (elegant in Ruby 1.9
anyway, I had to monkeypatch Ruby 1.8 to make it work) to the class
method problem. (It was exactly what I said before.)

So I think all the technical problems for this feature are solved, and
the question is just how the it should actually behave…

Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On 31 Jul 2010, at 7:06 PM, Myron M. wrote:

Good point–I hadn’t thought of that. The one issue I see with it is
that the author of the shared example group may not have knowledge of
which helper methods consumers will need to override. So he/she
either defines all helper methods that way, or guesses about which
ones to define that way (and potentially guesses wrong).

I wonder if this will happen in practice? I can’t think of an example
off the top of my head, which isn’t to say it won’t matter, but it may
be better done pull-based, when the need arises.

If we go the route of having the customization block evaluated first,
then I like the idea, but I’m generally wary of adding more DSL
methods to RSpec. I think we should be careful to only add new DSL
methods that many people will find useful. If you find it useful,
it’s very easy to use it in your project without it being part of
RSpec: just define default_helper in a module, and use config.extend
YourModule in the RSpec.configuration block. (Note that I’m not
against adding this to RSpec: I just want to be sure we don’t add a
bunch of DSL methods that have limited usefulness.)

This is a fair point. I’m going to the effort of implementing this
spike in rspec-core itself because I really want to see if there is
value in re-usable shared examples (my own, admittedly small,
side-project already suggests there is). But I’m fairly sure it’s not a
pattern in wide use, at least not with Ruby testing libraries.

end
def self.method_name; :foo; end
def method_name; :foo; end
end

I think this is a clunky way to essentially pass a parameter to the
shared example group.

Funny you mention this. While I’ve been working on my patch[1] I came
to the same conclusion. This (heavily trimmed down - I may have broken
it cutting bits out for email purposes) example demonstrates it:

module RSpec::Core
describe SharedExampleGroup::Requirements do
it “lets you specify requirements for shared example groups” do
shared_examples_for(“thing”) do
require_class_method :configuration_class_method, “message”

      it "lets you access #{configuration_class_method}s" do
        self.class.configuration_class_method.should eq 

“configuration_class_method”
end

      it "lets you access #{configuration_class_method}s" do
        configuration_class_method.should eq 

“configuration_class_method”
end
end

    group = ExampleGroup.describe("group") do
      it_should_behave_like "thing" do
        def self.configuration_class_method
          "configuration_class_method"
        end
      end
    end

    group.run_all.should be_true
  end
end

end

However, I found a serious issue with class methods, namely that they
are being defined in a persistent class, not a transient ExampleGroup
subclass. I haven’t investigated this yet*, but I’ve left a pending
spec at the appropriate point.

  • Random thought after seeing your code: using class << self; end over
    def self.x; end may be a partial answer?

I’ve written up an untested gist with a start for the code that would
implement this:

example_group.rb · GitHub

Thanks for writing this - it’s an interesting piece of code. Certainly
it also gets around the class/instance scope divide. But I don’t think
it can enforce that the parameter is provided? One of my hopes is to
make the errors completely self documenting.

Aside: one design decision I’ve made is to make every error due to a
missing requirement fail at the example level, rather than abort the
whole spec run. This is because RSpec-formatted requirements are MUCH
easier to read than a random stacktrace in a terminal. To do this, you
need to specify the class method requirement (comments added for
explanatory purposes):

def require_class_method(name, description)
if respond_to?(name)
# We have the class method, so alias it in the instance scope
define_method(name) do |*args|
self.class.send(name, *args)
end
else
# We don’t have the class method, so fail all the examples,
# but provide a class-level method so the example definitions
# doesn’t fail, and break the run
before(:each) do
raise ArgumentError.new(
%‘Shared example group requires class method :#{name}
(#{description})’
)
end
self.class.class_eval do
define_method(name) do |*args|
%‘<missing class method “#{name}”>’
end
end
end
end

I think there’s value in evaluating the customization block first and
value in evaluating it last. We can get the best of both worlds if we
limit what’s evaluated first to a subset (say, a few DSL methods, and
maybe all class method definitions), extract it, and evaluate that
first, then evaluate the shared block first, then evaluate the
customization block. The gist demonstrates this as well. This may
confuse people, but it does give us the best of both worlds, I think.

I think it’s fair to say this is not a simple one to resolve :slight_smile:

Maybe David has ideas on how to reconcile everything?

Cheers
Ash

[1]
http://github.com/ashleymoran/rspec-core/tree/issue_99_shared_example_block_ordering


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Jul 31, 2010, at 1:06 PM, Myron M. wrote:

You can still get the same outcome, but you have to implement it in the group like this:

unless defined?(:foo)
def foo; “foo”; end
end

Good point–I hadn’t thought of that. The one issue I see with it is
that the author of the shared example group may not have knowledge of
which helper methods consumers will need to override.

That’s no different from methods that have default values for arguments:

def foo(bar, baz = :default)

If you provide only 1 arg, all is well, but the first one is required.
Here’s the same idea expressed in a group:

shared_examples_for “foo” do
unless defined?(:bar)
raise “you need to supply a bar() method”
end

unless defined?(:baz)
def baz; :default; end
end
end

WDYT?

end
def self.method_name; :foo; end
The instance of the shared example group provides :foo as the value of
limit what’s evaluated first to a subset (say, a few DSL methods, and
maybe all class method definitions), extract it, and evaluate that
first, then evaluate the shared block first, then evaluate the
customization block. The gist demonstrates this as well. This may
confuse people, but it does give us the best of both worlds, I think.

Agreed on both points: best of both worlds and confusing :slight_smile:

When I said “maybe a DSL” I was thinking only in the context of the
shared group, not in the consuming group.

What makes the example in your gist confusing to me is that we start to
get into a different mental model of what a shared group is. Based on
recent changes, for me, it’s just a nested example group, which has well
understood scoping rules. This introduces a new set of scoping rules
that not only make this use case confusing, but it will lead to an
expectation that this DSL be made available in other constructs.

The particular issue of simple values being used in the docstrings and
the examples themselves (i.e. exposed to everything in the block scope)
could be handled like this:

shared_examples_for “blah” do |a,b|

end

it_should_behave_like “blah”, 1, 2

That wouldn’t have worked with the old implementation, but it would work
perfectly well now. This would also “just work” with
hash-as-keyword-args:

shared_examples_for “blah” do |options|
it “blah #{options[:a]}” do

end
end

it_should_behave_like “blah”, :a => 1

Now you can do this:

[1,2,3].each do |n|
it_should_behave_like “blah”, :a => n
end

And we can still use the customization block to define methods, hooks
(before/after) and let(). Now it just feels like the rest of RSpec.

Thoughts?

The particular issue of simple values being used in the docstrings and the examples themselves (i.e. exposed to everything in the block scope) could be handled like this:

shared_examples_for “blah” do |a,b|

end

it_should_behave_like “blah”, 1, 2

Fantastic idea. I’m sold. I’m not sure why this simple idea didn’t
occur to me earlier :(.

unless defined?(:baz)
def baz; :default; end
end
end

This does indeed work, to the extent that the methods the consumer
needs to override are ones the author of the shared example ground had
in mind, and coded as such. This isn’t an issue when the shared
example group and the consuming code are in the same code base. But
the idea has been brought up that shared example groups could be
provided by a library for users to use to enforce a contract of some
class or object they write that the library interacts with. I think
it’s likely that library authors won’t declare their helper methods
using the “unless defined?” idiom, because they can’t anticipate all
the needs of their users, and they probably aren’t even aware that
they have to declare their methods this way to allow them to be
overridden. Suddenly it’s impossible for consumers of the shared
example group to override any of the helper methods.

I love how flexible ruby is, and the fact that every method can be
overridden, without the original author of the a method having to do
anything special to allow it. Your suggestion above seems (to me
anyway) to be more in line with a language like C#, where methods are
not overriddable by default, and developers have to use the virtual
keyword to make them so.

So, all of that is just to say that I’m still in favor of eval’ing the
customization block last. To me, the primary need for eval’ing the
customization block first was to allow it define class helper methods
that the shared example group could use to interpolate into doc
strings, and this need is solved much more elegantly with David’s
suggestion. I like eval’ing it last so that helper methods can be
overridden, without anything special being done in the shared
example group.

Of course, if there are other things that will only work by eval’ing
the block first, then I’m completely fine with it–I’m just not seeing
the need right now.

Myron

On Aug 1, 2010, at 9:43 AM, David C. wrote:

that the author of the shared example group may not have knowledge of
raise “you need to supply a bar() method”

methods to RSpec. I think we should be careful to only add new DSL
example descriptions. (There may be other use cases for defining a
interpolate into the description string) and at the instance level
I think this is a clunky way to essentially pass a parameter to the
I’ve written up an untested gist with a start for the code that would
confuse people, but it does give us the best of both worlds, I think.

end

it_should_behave_like “blah”, :a => 1

Now you can do this:

[1,2,3].each do |n|
it_should_behave_like “blah”, :a => n
end

And we can still use the customization block to define methods, hooks (before/after) and let(). Now it just feels like the rest of RSpec.

Here’s one way the measurement example could work in this format:

On 1 Aug 2010, at 3:43 PM, David C. wrote:

And we can still use the customization block to define methods, hooks (before/after) and let(). Now it just feels like the rest of RSpec.

Thoughts?

One thought: me.facepalm :slight_smile:

The only thing it lacks is a DSL to define the requirements. Would it
still be desirable to be able to write:

shared_examples_for “blah” do |options|
require_argument options[:a]
it “blah #{options[:a]}” do

end
end

Or some such?

Also, after staring at this for a while, I’m puzzled by something. In
this code:

it_should_behave_like “blah”, :a => 1

how does :a => 1 get passed to the “options” block, as shared_block in
the code is never called with arguments? Would this need another code
change? (Apologies if I’m being thick, it’s late and I should probably
go to bed, but I wanted to review this first…)

Cheers
Ash


http://www.patchspace.co.uk/
http://www.linkedin.com/in/ashleymoran

On Aug 1, 2010, at 11:40 AM, Myron M. wrote:

provided by a library for users to use to enforce a contract of some
class or object they write that the library interacts with. I think
it’s likely that library authors won’t declare their helper methods
using the “unless defined?” idiom, because they can’t anticipate all
the needs of their users, and they probably aren’t even aware that
they have to declare their methods this way to allow them to be
overridden.

I kind of jumped around from one part of this thread to the
other - apologies if my responses seem to lack cohesion

That would be the tricky gotta RTFM part - I’d rather have that burden
on the shared group/library author than its consumer.

Suddenly it’s impossible for consumers of the shared
example group to override any of the helper methods.

I love how flexible ruby is, and the fact that every method can be
overridden, without the original author of the a method having to do
anything special to allow it. Your suggestion above seems (to me
anyway) to be more in line with a language like C#, where methods are
not overriddable by default, and developers have to use the virtual
keyword to make them so.

Seems like your mental model is that of a customization block being a
subclass or re-opening of the shared block. What you say makes sense in
that model, but that’s not the same model I have.

If we were going to go with the subclass model, I think we’d be best
served by going all the way there. So this structure:

describe “foo” do
it_behaves_like “bar”, “with baz == 3” do
def baz; 3 end
end
end

Would result in:

foo # top level group
it behaves like bar # nested group generated by it_behaves_like using
“bar” in the report
with baz == 3 # 3rd level nested group generated using the
customization block

This would make the whole relationships between things much more
transparent. I don’t love this idea either, but we’re searching for
balance here. At least I am :slight_smile:

So, all of that is just to say that I’m still in favor of eval’ing the
customization block last. To me, the primary need for eval’ing the
customization block first was to allow it define class helper methods
that the shared example group could use to interpolate into doc
strings, and this need is solved much more elegantly with David’s
suggestion.

Assuming that can work. I’ve taken a closer look and getting that to
work would take some serious re-architecting that I’m not sure is a good
idea. Consider the code as it is now:

I’ve restructured it a bit after some of this conversation, but right
now the customization block is still eval’d last. I think this code is
very easy to grok for a reasonably advanced Rubyist (i.e. if you can get
past module_eval(<<-END_RUBY), then the content of the String is no
problem), but if we start adding gymnastics to support various
combinations of nice-to-haves, then this code will quickly become harder
to read.

In my experience with RSpec, readability/simplicity of the internals
does matter to end users, not just contributors and maintainers,
because many want to understand what’s going on under the hood. That is
a strong motivator for me to keep things exactly as they are now (simple
and readable).

In terms of end-users, the consumer of the shared group would not need
to be any different in either of these two scenarios:

shared_examples_for “foo” do

with customization_block eval’d before

unless defined?(:bar)
raise “you need to supply a bar() method”
end
end

shared_examples_for “foo” do

with customization_block eval’d after

def bar
raise “you need to supply a bar() method”
end
end

In either case, this will get you the same error:

it “does something” do
it_behaves_like “bar”
end

I like eval’ing it last so that helper methods can be
overridden, without anything special being done in the shared
example group.

I can appreciate that but I’d rather have burden placed on the shared
group author than its consumer.

Cheers,
David