Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Aliasing API for #describe #870

Closed
wants to merge 15 commits into from
Closed

Aliasing API for #describe #870

wants to merge 15 commits into from

Conversation

michihuber
Copy link
Contributor

This adds the ability to configure aliases for #describe with default metadata, as requested in #493.

  • Renames ExampleGroup.describe to .example_group (and aliases it back to .describe).
  • Adds Configuration#alias_example_group_to, which accepts default metadata and a :toplevel_alias option.
  • Adds --toplevel-off flag, which avoids extending the main object with the DSL methods (i.e. #describe and defined aliases).

@@ -604,6 +604,13 @@ def alias_example_to(new_name, *args)
RSpec::Core::ExampleGroup.alias_example_to(new_name, extra_options)
end

def alias_example_group_to(new_name, *metadata_and_opts)
top_level_method = !!metadata_and_opts.delete(:toplevel_alias)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has the side effect of modifying the passed in hash. Consider a case like this:

RSpec.configure do |c|
  focused_options = { focus: true, toplevel_alias: true }
  c.alias_example_group_to :focus, focused_options
  c.alias_example_group_to :focused, focused_options
end

The 2nd one will not work properly because the first one modifies the passed in options.

I think it's better to dup the hash before deleting from it.

@myronmarston
Copy link
Member

@michihuber -- fantastic work. This is very well written! A few additional thoughts:

  • When the top-level DSL methods are turned off, how are users supposed to declare example groups? That's not documented anywhere. I know (from working on RSpec) that users can use RSpec::Core::ExampleGroup.describe -- but that's not obvious to end users, is not documented anywhere, is kinda an ugly API and isn't really meant to be used publicly. What about always extending the DSL module onto RSpec? Then when folks turn off the top-level DSL, they could still do:
RSpec.shared_examples_for "some behavior" do
end

RSpec.describe MyClass do
end
  • This combined with New syntax rspec-mocks#266 gets us within range of having an RSpec that doesn't monkey patch anything...which would be pretty awesome. My initial thought was that maybe the --toplevel-off (or whatever we call it) option should default to true in 3.0, but now I'm thinking that it's a big change, and we should simply advertise that in rspec 3 you'll be able to use it w/o any monkey patching, but then in 4.0 we can actually make it the default if the community is behind it by that point. This sort of migration path will be a lot friendlier to newbies that are using old resources to try out rspec. Thoughts from others on this?

@myronmarston
Copy link
Member

BTW, in some of my comments above I mentioned "the original issue"...I actually meant #790 (the one I knew of and had participated in), not #493 -- I didn't even realize that's been a long-standing open issue.

@michihuber
Copy link
Contributor Author

Thanks for your detailed comments, @myronmarston, much appreciated! I totally missed #790, but reread it now. You weren't sure about toplevel support, what do you think about it now? I think the overhead is reasonable, but it would introduce a "reserved" metadata key, as you pointed out above. What about a different method? #toplevel_alias_example_group_to?

I wanted to get input about where the DSL should be included in first, I'll add it to RSpec.

I'll fix the stuff you commented on (and the failing specs) in the next few days.

@michihuber
Copy link
Contributor Author

From my side, a 3.0 release without 1.8.6 is fine, but I'd make the necessary changes of course if somebody needs it earlier.

@myronmarston
Copy link
Member

You weren't sure about toplevel support, what do you think about it now? I think the overhead is reasonable, but it would introduce a "reserved" metadata key, as you pointed out above.

I like the feature a lot now, because it gives us the possibility of having a zero-monkey-patching RSpec. That's really cool.

As for the reserved metadata key...I'm on the fence about it. We already have a couple of those (e.g. pending). It can lead to surprises though.

What about a different method? #toplevel_alias_example_group_to?

Maybe...problem is, that suggests that the alias is only at the top level. More thought is needed, I think.

/cc @JonRowe @alindeman @samphippen @soulcutter

@JonRowe
Copy link
Member

JonRowe commented Apr 13, 2013

I also like the idea of having zero monkey patching :) but I think we'd definitely want an easy way to access the DSL if top level was turned off.. I'm not sure I like polluting RSpec directly though... maybe we could go with something like RSpec::DSL.describe?

Also, crazy thought but could we disable mixing in describe to the top level, but then have the runner eval _spec files within the context of that DSL? Thus removing monkey patching but also allowing a legacy style?

@michihuber
Copy link
Contributor Author

I've addressed most of @myronmarston's comments.

Some open questions:

  • From where should the DSL be proxied? RSpec? RSpec::DSL?
  • How should aliases to the top-level be distinguished from nested aliases? Two distinct methods avoid the reserved keyword problem, so I think that makes sense, but a fitting name is needed.
  • Regarding a toplevel_dsl config option: that was actually the approach I took first, but I guess this could lead to lots of surprises. I'll take another stab at it and try to find a good way to guide through warnings etc.
  • I also really like @JonRowe's idea (evaluating the spec files in the DSL's scope). I took a quick stab today and it looks very promising, I'll try to work out the remaining kinks in the next view days (most of them have to do with running specs with line numbers).

@myronmarston
Copy link
Member

I'm not sure I like polluting RSpec directly though... maybe we could go with something like RSpec::DSL.describe?

What problem do you see with "polluting" RSpec? I don't actually see it as polluting RSpec at all; I see it as exposing the API in the form we want it. I don't think we need the ceremony of Nested::Constants, and I can't think of a single problem this will cause. For end users, I think RSpec.describe feels very simple and straightforward in a way that RSpec::DSL.describe doesn't.

Also, crazy thought but could we disable mixing in describe to the top level, but then have the runner eval _spec files within the context of that DSL? Thus removing monkey patching but also allowing a legacy style?

This is a very interesting idea that has never occurred to me! (That's the great thing about bringing new folks on board; they bring new ideas...) However, I'm concerned about the repercussions of such a change:

  • Sometimes folks rely on self at the top level of their spec files being main; for example here's one place where I did that. If we made this change, such things would break.
  • Other times, folks define levels of the top level, expecting them to be added as helper methods to all objects. This works when files are loaded normally, but would not work if we eval files in a different context.
  • In general, I think it's best for RSpec to do as much "normal" ruby stuff, and as little surprising, non-standard things as possible. Changing what context spec files are eval'd in would be a very surprising change, I think. I don't think the benefit is large enough to warrant making the change. There are likely to be unforeseen negative repercussions, I think

@JonRowe
Copy link
Member

JonRowe commented Apr 14, 2013

What problem do you see with "polluting" RSpec? I don't actually see it as polluting RSpec at all; I see it as exposing the API in the form we want it. I don't think we need the ceremony of Nested::Constants, and I can't think of a single problem this will cause. For end users, I think RSpec.describe feels very simple and straightforward in a way that RSpec::DSL.describe doesn't.

Fair enough, I just edge away from everything being on the top level, but I guess a .describe is pretty core functionality... I accept your reasoning 👍

Sometimes folks rely on self at the top level of their spec files being main; for example here's one place where I did that. If we made this change, such things would break.

Hmm. We could make main available to people to work around this, altho I feel it's very edge case?

Other times, folks define levels of the top level, expecting them to be added as helper methods to all objects. This works when files are loaded normally, but would not work if we eval files in a different context.

They should continue to work as is inside RSpec, as everything would be nested... Support files would also continue to be defined from main.

In general, I think it's best for RSpec to do as much "normal" ruby stuff, and as little surprising, non-standard things as possible.

Agreed.

Changing what context spec files are eval'd in would be a very surprising change, I think. I don't think the benefit is large enough to warrant making the change. There are likely to be unforeseen negative repercussions, I think

Like I said it was a crazy thought, I was mostly concerned with how we could remove monkey patching whilst preserving existing functionality for our users.

@JonRowe
Copy link
Member

JonRowe commented Dec 7, 2013

@michihuber want to try rebasing this now the monkey patching PR is merged?

@myronmarston
Copy link
Member

BTW, regarding whether or not this config option should add top-level describe alias: I think that should depend on the setting @JonRowe added in #1036. That'll be cleaner than forcing the user to pass a particular hash key to specify that. We should probably also make context top-level (or not) based on that new config option.

@michihuber
Copy link
Contributor Author

Sure, I’ll update this to use @JonRowe’s new setting in the upcoming week.

On Saturday 7 December 2013 at 17:42, Myron Marston wrote:

BTW, regarding whether or not this config option should add top-level describe alias: I think that should depend on the setting @JonRowe (https://github.com/JonRowe) added in #1036 (#1036). That'll be cleaner than forcing the user to pass a particular hash key to specify that. We should probably also make context top-level (or not) based on that new config option.


Reply to this email directly or view it on GitHub (#870 (comment)).

@michihuber
Copy link
Contributor Author

@myronmarston, I think it might be useful to allow making describe available from the top level without also adding all other example_group aliases (context or custom ones). I.e. @JonRowe's expose_globally should only control the scope of the API, not the content.

What about two different config methods instead of a reserved hash key? I proposed alias_example_group_to and toplevel_alias_example_group_to, but you were suggesting that the latter might imply that the alias would only exist at the top level.

Maybe finding a new method name might be worthwhile and less confusing than expose_globally changing scope and content.

@JonRowe
Copy link
Member

JonRowe commented Dec 15, 2013

@michihuber you'll note there will need to be some rejigging to make my code work with custom aliases, so it'd be nice to refactor it to add the default aliases but also any custom ones in the same mechanism

@myronmarston
Copy link
Member

@myronmarston, I think it might be useful to allow making describe available from the top level without also adding all other example_group aliases (context or custom ones). I.e. @JonRowe's expose_globally should only control the scope of the API, not the content.

Why is that useful? It's unclear to me. FWIW, context used to be available at the top-level, but it was removed because at the time the implementation added context to every object, and led to some conflicts. Now we have a much better, safer way of exposing that top-level DSL that adds it only to main and modules, and does not add it to every object. If a user doesn't want to use a particular alias from the top-level...then they should simply not call that method. I don't think it's worth the complexity of adding additional config options, now that we have the expose_dsl_globally for toggling it on and off at the top level.

@michihuber
Copy link
Contributor Author

Sure, that makes sense.
I didn’t realise before that you wanted to make context an alias for describe by default (as I also didn’t know how that difference came about).

On Monday 16 December 2013 at 01:10, Myron Marston wrote:

@myronmarston (https://github.com/myronmarston), I think it might be useful to allow making describe available from the top level without also adding all other example_group aliases (context or custom ones). I.e. @JonRowe (https://github.com/JonRowe)'s expose_globally should only control the scope of the API, not the content.

Why is that useful? It's unclear to me. FWIW, context used to be available at the top-level, but it was removed because at the time the implementation added context to every object, and led to some conflicts. Now we have a much better, safer way of exposing that top-level DSL that adds it only to main and modules, and does not add it to every object. If a user doesn't want to use a particular alias from the top-level...then they should simply not call that method. I don't think it's worth the complexity of adding additional config options, now that we have the expose_dsl_globally for toggling it on and off at the top level.


Reply to this email directly or view it on GitHub (#870 (comment)).

@myronmarston
Copy link
Member

Close in favor of #1236.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants