Ashley Moran
2010-Jul-30 10:13 UTC
[rspec-users] 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
David Chelimsky
2010-Jul-30 14:03 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 30, 2010, at 5:13 AM, Ashley Moran wrote:> 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 >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 examplesMakes perfect sense to me. Wanna make a patch with an additional scenario in the cuke?> > Cheers > Ash > > -- > http://www.patchspace.co.uk/ > http://www.linkedin.com/in/ashleymoran > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
David Chelimsky
2010-Jul-30 14:10 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 30, 2010, at 9:03 AM, David Chelimsky wrote:> > On Jul 30, 2010, at 5:13 AM, Ashley Moran wrote: > >> 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 >> > > 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?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?> >> >> Cheers >> Ash >> >> -- >> http://www.patchspace.co.uk/ >> http://www.linkedin.com/in/ashleymoran >> >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >
Wincent Colaiuta
2010-Jul-30 15:17 UTC
[rspec-users] Evaluating shared example customisation block before shared block
El 30/07/2010, a las 16:10, David Chelimsky escribi?:> 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?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
David Chelimsky
2010-Jul-30 15:47 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 30, 2010, at 10:17 AM, Wincent Colaiuta wrote:> El 30/07/2010, a las 16:10, David Chelimsky escribi?: > >> 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? > > 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.)That would only work if people read the docs :) 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?> > Cheers, > Wincent > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Ashley Moran
2010-Jul-30 15:57 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 30, 2010, at 3:10 pm, David Chelimsky 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
David Chelimsky
2010-Jul-30 16:00 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 30, 2010, at 10:57 AM, Ashley Moran wrote:> > On Jul 30, 2010, at 3:10 pm, David Chelimsky 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.By all means.> Regards > Ash
Ashley Moran
2010-Jul-30 16:21 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 30, 2010, at 4:47 pm, David Chelimsky 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
Ashley Moran
2010-Jul-30 21:58 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 30 Jul 2010, at 5:00 PM, David Chelimsky 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] http://github.com/rspec/rspec-core/issues/issue/99 -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Myron Marston
2010-Jul-30 23:56 UTC
[rspec-users] Evaluating shared example customisation block before shared block
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 Moran <ashley.mo... at patchspace.co.uk> wrote:> On 30 Jul 2010, at 5:00 PM, David Chelimsky 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]http://github.com/rspec/rspec-core/issues/issue/99 > > --http://www.patchspace.co.uk/http://www.linkedin.com/in/ashleymoran > > _______________________________________________ > rspec-users mailing list > rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
David Chelimsky
2010-Jul-31 00:10 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 30, 2010, at 6:56 PM, Myron Marston wrote:> On Jul 30, 2:58 pm, Ashley Moran <ashley.mo... at patchspace.co.uk> > wrote: >> On 30 Jul 2010, at 5:00 PM, David Chelimsky 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]http://github.com/rspec/rspec-core/issues/issue/99 >> >> --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.
Ashley Moran
2010-Jul-31 07:56 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 31 Jul 2010, at 1:10 AM, David Chelimsky 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 > endMaybe 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
Myron Marston
2010-Jul-31 18:06 UTC
[rspec-users] Evaluating shared example customisation block before shared block
> 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 > endGood 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: http://gist.github.com/502409 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 Moran <ashley.mo... at patchspace.co.uk> wrote:> On 31 Jul 2010, at 1:10 AM, David Chelimsky 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 > > _______________________________________________ > rspec-users mailing list > rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users
Ashley Moran
2010-Jul-31 19:42 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 31 Jul 2010, at 7:06 PM, Myron Marston 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.> 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.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?> 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: > > http://gist.github.com/502409Thanks 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 :) 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
Ashley Moran
2010-Aug-01 09:36 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 31, 2010, at 7:06 pm, Myron Marston 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 > endAfter 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
David Chelimsky
2010-Aug-01 14:43 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Jul 31, 2010, at 1:06 PM, Myron Marston 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> 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: > > http://gist.github.com/502409 > > 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.Agreed on both points: best of both worlds and confusing :) 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?> > Myron > > > On Jul 31, 12:56 am, Ashley Moran <ashley.mo... at patchspace.co.uk> > wrote: >> On 31 Jul 2010, at 1:10 AM, David Chelimsky 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 >> >> _______________________________________________ >> rspec-users mailing list >> rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
David Chelimsky
2010-Aug-01 14:48 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 1, 2010, at 9:43 AM, David Chelimsky wrote:> > On Jul 31, 2010, at 1:06 PM, Myron Marston 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 > >> 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: >> >> http://gist.github.com/502409 >> >> 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. > > Agreed on both points: best of both worlds and confusing :) > > 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.Here''s one way the measurement example could work in this format: http://gist.github.com/503432> > Thoughts? > > > >> >> Myron >> >> >> On Jul 31, 12:56 am, Ashley Moran <ashley.mo... at patchspace.co.uk> >> wrote: >>> On 31 Jul 2010, at 1:10 AM, David Chelimsky 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 >>> >>> _______________________________________________ >>> rspec-users mailing list >>> rspec-us... at rubyforge.orghttp://rubyforge.org/mailman/listinfo/rspec-users >> _______________________________________________ >> rspec-users mailing list >> rspec-users at rubyforge.org >> http://rubyforge.org/mailman/listinfo/rspec-users >
Myron Marston
2010-Aug-01 16:40 UTC
[rspec-users] Evaluating shared example customisation block before shared block
> 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, 2Fantastic idea. I''m sold. I''m not sure why this simple idea didn''t occur to me earlier :(.> 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 > endThis 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
David Chelimsky
2010-Aug-01 19:02 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 1, 2010, at 11:40 AM, Myron Marston wrote:>> 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 :(. > >> 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 > > 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.<disclaimer>I kind of jumped around from one part of this thread to the other - apologies if my responses seem to lack cohesion</disclaimer> 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 :)> 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: http://github.com/rspec/rspec-core/blob/cc72146205fb93ca11e1f290d3385151b51181ad/lib/rspec/core/example_group.rb#L61 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> 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 > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Ashley Moran
2010-Aug-01 22:12 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 1 Aug 2010, at 3:43 PM, David Chelimsky wrote:> 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?One thought: me.facepalm :) 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
Myron Marston
2010-Aug-01 22:39 UTC
[rspec-users] Evaluating shared example customisation block before shared block
> 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.My mental model is indeed that the customization block is like a subclass. I''m not sure where I got it--it''s just the intuitive way I understood shared_examples_for and it_should_behave_like. But if no one else shares this mental model, then there''s not much point in making rspec work this way. I''m happy going with whatever the general consensus is. Although, I do think that my mental model makes for some interesting possibilities :).> 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.Maybe I misunderstood you here, but I took this to refer to the passing of parameters to the shared example group, as you suggested...and it turns out this isn''t very hard at all: http://github.com/myronmarston/rspec-core/commit/c353badcb8154ab98a7dc46eb19c8a9fc702ec73 The one issue with this is that it uses #module_exec, which is not available in ruby 1.8.6--so we''d have to find a way to implement it, similar to how cucumber implements #instance_exec when it''s not available: http://github.com/aslakhellesoy/cucumber/blob/30d43767a7cffd1675e990115ac86c139e4ea3e0/lib/cucumber/core_ext/instance_exec.rb#L16-31 Myron
David Chelimsky
2010-Aug-01 22:52 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 1, 2010, at 5:12 PM, Ashley Moran wrote:> > On 1 Aug 2010, at 3:43 PM, David Chelimsky wrote: > >> 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? > > One thought: me.facepalm :) > > 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...)Actually, I just discovered that ruby 1.8.7 actually added module_exec. What does this mean? It means this: def self.define_shared_group_method(new_name, report_label=nil) module_eval(<<-END_RUBY, __FILE__, __LINE__) def self.#{new_name}(name, *args, &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 || "it should behave like"} \#{name}") do module_exec *args, &shared_block module_exec *args, &customization_block if customization_block end end END_RUBY end What does that mean? It means this: shared_examples_for "foo" do |a,b,c| it "#{a} #{b} #{c}s" do a.should do_something_with(b, c) end end describe "something" do it_behaves_like "foo", 1, 2, 3 end Ta da!!!!!! Two problems to solve at this point: 1. order of evaluation of blocks 2. what to do about ruby 1.8.6 re: order of evaluation of blocks, I think I''m inclined to go one way one minute, and another the next. Somebody convince me of one or the other. re: 1.8.6, we''ve got a home-grown implementation of instance_exec that runs in 1.8.6 (although I just discovered that it''s broken - fix coming shortly). I could a) add such a thing for module_exec as well, though I haven''t quite figured out how that works yet. b) only support parameterized shared groups in ruby 1.8.7 or better c). the most drastic option, would be to drop support for 1.8.6 entirely, but I don''t think that''s really feasible yet. Thoughts? David> Cheers > Ash
David Chelimsky
2010-Aug-01 23:00 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 1, 2010, at 5:39 PM, Myron Marston wrote:>> 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. > > My mental model is indeed that the customization block is like a > subclass. I''m not sure where I got it--it''s just the intuitive way I > understood shared_examples_for and it_should_behave_like. But if no > one else shares this mental model, then there''s not much point in > making rspec work this way. I''m happy going with whatever the general > consensus is. Although, I do think that my mental model makes for > some interesting possibilities :). > >> 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. > > Maybe I misunderstood you here, but I took this to refer to the > passing of parameters to the shared example group, as you > suggested...and it turns out this isn''t very hard at all: > > http://github.com/myronmarston/rspec-core/commit/c353badcb8154ab98a7dc46eb19c8a9fc702ec73If we do this, we should use module_exec for both blocks so they both get the same arguments.> > The one issue with this is that it uses #module_exec, which is not > available in ruby 1.8.6--so we''d have to find a way to implement it, > similar to how cucumber implements #instance_exec when it''s not > available: > > http://github.com/aslakhellesoy/cucumber/blob/30d43767a7cffd1675e990115ac86c139e4ea3e0/lib/cucumber/core_ext/instance_exec.rb#L16-31RSpec does that too :). Pretty sure it was Aslak that added that some years back. Yeah - I''m playing around with an implementation of module_exec, but it doesn''t seem to work quite the same way as instance exec does. Not yet, anyhow. David> > Myron > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Myron Marston
2010-Aug-02 01:04 UTC
[rspec-users] Evaluating shared example customisation block before shared block
> If we do this, we should use module_exec for both blocks so they both get the same arguments.I actually find the use of this to be a bit confusing: [:foo, :bar].each do |arg| it_should_behave_like "Something", arg do |a| # The value of the param is already bound to arg and now it''s bound to a, too. end end I suppose it may be useful in some situations, so I''m fine with it as long as the implementation allows you to skip the `|a|`: [:foo, :bar].each do |arg| it_should_behave_like "Something", arg do # no need to declare the |a| parameter since we already have it in arg. # I find this to be less confusing. end end> Actually, I just discovered that ruby 1.8.7 actually added module_exec.I should have mentioned that--my default ruby these days is 1.8.7, and I found the same thing.> re: order of evaluation of blocks, I think I''m inclined to go one way one minute, and another the next. Somebody convince me of one or the other.Maybe it would be useful to make a list of the things that are possible one way, but not the other, and vice versa...and then compare these lists. Which list has the more useful and commonly needed use cases?> a) add such a thing for module_exec as well, though I haven''t quite figured out how that works yet.This is my vote. And I''m willing to take a stab at it--if for no other reason then it''ll help increase my understanding of ruby :). I''m going to take a look at how rubinius implements this, and see what ruby specs there are for it. Myron
Myron Marston
2010-Aug-02 03:08 UTC
[rspec-users] Evaluating shared example customisation block before shared block
OK, I tried to implement #module_exec on ruby 1.8.6, and here''s what I came up with: http://github.com/myronmarston/rspec-core/commit/364f20ebd5b7d9612227cb6e86a6e8c8c2e9931e It works (at least in the sense that it allows the specs and features I added in the previous commits in that branch to pass on ruby 1.8.6), but I don''t think it''s correct. It just calls #instance_exec, but instance_exec and module_exec are not the same (at least as I understand them...). However, I based that implementation on rubinius 1.0.1''s: http://github.com/evanphx/rubinius/blob/release-1.0.1/kernel/common/module.rb#L438-441 Backports (a library that implements features of later versions of ruby in 1.8.6) implements it in a similar fashion: http://github.com/marcandre/backports/blob/v1.18.1/lib/backports/1.8.7/module.rb Unfortunately, rubyspec doesn''t provide much help in defining how module_exec should work: http://github.com/rubyspec/rubyspec/blob/master/core/module/module_exec_spec.rb I''d need some concrete examples demonstrating how module_exec should work (as compared to instance_exec) to implement it correctly. My understanding is that they are identical except in regards to method definitions--def''s within an instance_exec define methods directly on the Module object instance, whereas def''s within a module_exec define instance methods in the module (i.e. methods that will be added to any class that includes the module). One side issue I discovered is that instance_exec is implemented in rspec-expectations, but not rspec-core (which, incidentally, is why I linked to the cucumber implementation; I grepped in rspec-core and couldn''t find it). instance_exec is already used in rspec-core: http://github.com/rspec/rspec-core/blob/v2.0.0.beta.19/lib/rspec/core/example_group.rb#L179 It needs to be implemented in rspec-core if you want rspec-core to be able to be used on 1.8.6 without rspec-expectations. Myron
Ashley Moran
2010-Aug-02 09:49 UTC
[rspec-users] RSpec 2 Ruby 1.8.6 support (was Evaluating shared example customisation block before shared block)
On Aug 01, 2010, at 11:52 pm, David Chelimsky wrote:> re: 1.8.6, we''ve got a home-grown implementation of instance_exec that runs in 1.8.6 (although I just discovered that it''s broken - fix coming shortly). I could > > a) add such a thing for module_exec as well, though I haven''t quite figured out how that works yet. > b) only support parameterized shared groups in ruby 1.8.7 or better > c). the most drastic option, would be to drop support for 1.8.6 entirely, but I don''t think that''s really feasible yet.Hmmm. If you''re working on a Rails project with RSpec 2 (which I''m not, but I''m guessing that will be a very common case), you need 1.8.7 anyway, as Rails 3 won''t run on anything less. If you''re not using Rails, I can''t imagine anyone starting a new project on 1.8.6 now. (All my new stuff is on 1.9.2.) Is 1.8.6 support in RSpec 2 *really* necessary? Any thoughts from anyone? Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
David Chelimsky
2010-Aug-02 12:08 UTC
[rspec-users] RSpec 2 Ruby 1.8.6 support (was Evaluating shared example customisation block before shared block)
On Aug 2, 2010, at 4:49 AM, Ashley Moran wrote:> > On Aug 01, 2010, at 11:52 pm, David Chelimsky wrote: > >> re: 1.8.6, we''ve got a home-grown implementation of instance_exec that runs in 1.8.6 (although I just discovered that it''s broken - fix coming shortly). I could >> >> a) add such a thing for module_exec as well, though I haven''t quite figured out how that works yet. >> b) only support parameterized shared groups in ruby 1.8.7 or better >> c). the most drastic option, would be to drop support for 1.8.6 entirely, but I don''t think that''s really feasible yet. > > Hmmm. If you''re working on a Rails project with RSpec 2 (which I''m not, but I''m guessing that will be a very common case), you need 1.8.7 anyway, as Rails 3 won''t run on anything less. If you''re not using Rails, I can''t imagine anyone starting a new project on 1.8.6 now. (All my new stuff is on 1.9.2.)But what about people who are, for what ever reasons, stuck with Ruby 1.8.6 and want to upgrade? Also, there are a few rspec-2 + rails-2 efforts in the works, and there will be a solution for this sometime this fall. We need to support 1.8.6.> > Is 1.8.6 support in RSpec 2 *really* necessary? Any thoughts from anyone? > > Cheers > Ash > > -- > http://www.patchspace.co.uk/ > http://www.linkedin.com/in/ashleymoran > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
David Chelimsky
2010-Aug-02 12:52 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 1, 2010, at 10:08 PM, Myron Marston wrote:> OK, I tried to implement #module_exec on ruby 1.8.6, and here''s what I > came up with: > > http://github.com/myronmarston/rspec-core/commit/364f20ebd5b7d9612227cb6e86a6e8c8c2e9931e > > It works (at least in the sense that it allows the specs and features > I added in the previous commits in that branch to pass on ruby 1.8.6), > but I don''t think it''s correct.If we''re not exposing this as an API, and we''re only using it in order to support this feature in RSpec when running Ruby 1.8.6, and it solves our problem, then I think it''s correct as it needs to be.> It just calls #instance_exec, but > instance_exec and module_exec are not the same (at least as I > understand them...). However, I based that implementation on rubinius > 1.0.1''s: > > http://github.com/evanphx/rubinius/blob/release-1.0.1/kernel/common/module.rb#L438-441 > > Backports (a library that implements features of later versions of > ruby in 1.8.6) implements it in a similar fashion: > > http://github.com/marcandre/backports/blob/v1.18.1/lib/backports/1.8.7/module.rb > > Unfortunately, rubyspec doesn''t provide much help in defining how > module_exec should work: > > http://github.com/rubyspec/rubyspec/blob/master/core/module/module_exec_spec.rb > > I''d need some concrete examples demonstrating how module_exec should > work (as compared to instance_exec) to implement it correctly. My > understanding is that they are identical except in regards to method > definitions--def''s within an instance_exec define methods directly on > the Module object instance, whereas def''s within a module_exec define > instance methods in the module (i.e. methods that will be added to any > class that includes the module). > > One side issue I discovered is that instance_exec is implemented in > rspec-expectations, but not rspec-core (which, incidentally, is why I > linked to the cucumber implementation; I grepped in rspec-core and > couldn''t find it). instance_exec is already used in rspec-core: > > http://github.com/rspec/rspec-core/blob/v2.0.0.beta.19/lib/rspec/core/example_group.rb#L179 > > It needs to be implemented in rspec-core if you want rspec-core to be > able to be used on 1.8.6 without rspec-expectations.Done: http://github.com/rspec/rspec-core/commit/7d492bdc3657ca9472368b50f3f3f6635aca7fe0> > Myron
Ashley Moran
2010-Aug-02 13:01 UTC
[rspec-users] RSpec 2 Ruby 1.8.6 support (was Evaluating shared example customisation block before shared block)
On 2 Aug 2010, at 1:08 PM, David Chelimsky wrote:> But what about people who are, for what ever reasons, stuck with Ruby 1.8.6 and want to upgrade? Also, there are a few rspec-2 + rails-2 efforts in the works, and there will be a solution for this sometime this fall. > > We need to support 1.8.6.Ah fair enough. I didn''t imagine there were many people stuck in that situation - I thought 1.8.6 was largely obsolete now. I also didn''t know there were any RSpec-2/Rails-2 solutions in progress, I thought that was going to be unsupported. -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Ashley Moran
2010-Aug-02 13:16 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 2 Aug 2010, at 2:04 AM, Myron Marston wrote:> I actually find the use of this to be a bit confusing: > > [:foo, :bar].each do |arg| > it_should_behave_like "Something", arg do |a| > # The value of the param is already bound to arg and now it''s > bound to a, too. > end > end > > I suppose it may be useful in some situations, so I''m fine with it as > long as the implementation allows you to skip the `|a|`: > > [:foo, :bar].each do |arg| > it_should_behave_like "Something", arg do > # no need to declare the |a| parameter since we already have it in > arg. > # I find this to be less confusing. > end > endAgreed - requiring the block parameter to be declared in the host group is putting the onus back on the user, not the author, of the shared examples. I think we need a way to implement the second version. Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Ashley Moran
2010-Aug-02 13:19 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 2 Aug 2010, at 4:08 AM, Myron Marston wrote:> Backports (a library that implements features of later versions of > ruby in 1.8.6) implements it in a similar fashion: > > http://github.com/marcandre/backports/blob/v1.18.1/lib/backports/1.8.7/module.rbConceivably, RSpec 2 could depend on Backports under Ruby 1.8.6. It''s not my opinion that it should (I don''t have one), but I''m interested to know the implications. Would that solve any design problems inside RSpec? Would that cause any problems for users? Would the problems solved outweigh the problems used? Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Ashley Moran
2010-Aug-02 13:23 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 1 Aug 2010, at 11:52 PM, David Chelimsky wrote:> re: order of evaluation of blocks, I think I''m inclined to go one way one minute, and another the next. Somebody convince me of one or the other.One thing that may help clear this up is: can anyone offer a concrete example of where overriding a shared example group helper method would be useful (and better than an alternative design)? My gut feeling is that, as David suggested, being able to override these is creating a class hierarchy of example groups. It feels to me unnervingly like overriding private methods. I wait to be proved wrong though, I just can''t think of an example myself of wanting to do this. Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
David Chelimsky
2010-Aug-03 11:22 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 2, 2010, at 7:52 AM, David Chelimsky wrote:> On Aug 1, 2010, at 10:08 PM, Myron Marston wrote: > >> OK, I tried to implement #module_exec on ruby 1.8.6, and here''s what I >> came up with: >> >> http://github.com/myronmarston/rspec-core/commit/364f20ebd5b7d9612227cb6e86a6e8c8c2e9931e >> >> It works (at least in the sense that it allows the specs and features >> I added in the previous commits in that branch to pass on ruby 1.8.6), >> but I don''t think it''s correct. > > If we''re not exposing this as an API, and we''re only using it in order to support this feature in RSpec when running Ruby 1.8.6, and it solves our problem, then I think it''s correct as it needs to be.These are all true except for the "and it solves our problem" part. It almost does, but the one missing piece is that methods defined in the shared group are not available to its examples: shared_examples_for "thing" do def thing; Thing.new; do it "does something" do thing.should do_something end end My inclination is to get this feature out with explicit non-support for 1.8.6, and then add support for 1.8.6 if we can get this to work. Working on that now - should be pushing some code (including Myron''s contribution) later today. Cheers, David> >> It just calls #instance_exec, but >> instance_exec and module_exec are not the same (at least as I >> understand them...). However, I based that implementation on rubinius >> 1.0.1''s: >> >> http://github.com/evanphx/rubinius/blob/release-1.0.1/kernel/common/module.rb#L438-441 >> >> Backports (a library that implements features of later versions of >> ruby in 1.8.6) implements it in a similar fashion: >> >> http://github.com/marcandre/backports/blob/v1.18.1/lib/backports/1.8.7/module.rb >> >> Unfortunately, rubyspec doesn''t provide much help in defining how >> module_exec should work: >> >> http://github.com/rubyspec/rubyspec/blob/master/core/module/module_exec_spec.rb >> >> I''d need some concrete examples demonstrating how module_exec should >> work (as compared to instance_exec) to implement it correctly. My >> understanding is that they are identical except in regards to method >> definitions--def''s within an instance_exec define methods directly on >> the Module object instance, whereas def''s within a module_exec define >> instance methods in the module (i.e. methods that will be added to any >> class that includes the module). >> >> One side issue I discovered is that instance_exec is implemented in >> rspec-expectations, but not rspec-core (which, incidentally, is why I >> linked to the cucumber implementation; I grepped in rspec-core and >> couldn''t find it). instance_exec is already used in rspec-core: >> >> http://github.com/rspec/rspec-core/blob/v2.0.0.beta.19/lib/rspec/core/example_group.rb#L179 >> >> It needs to be implemented in rspec-core if you want rspec-core to be >> able to be used on 1.8.6 without rspec-expectations. > > Done: http://github.com/rspec/rspec-core/commit/7d492bdc3657ca9472368b50f3f3f6635aca7fe0 > >> >> Myron >
Ashley Moran
2010-Aug-03 11:43 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 03, 2010, at 12:22 pm, David Chelimsky wrote:> My inclination is to get this feature out with explicit non-support for 1.8.6, and then add support for 1.8.6 if we can get this to work. Working on that now - should be pushing some code (including Myron''s contribution) later today.Do you have everything in place to finish this off? Happy to help out if you want me to do any more coding on this, but it sounds like you''ve figured out a solution. In which case I''ll sit back and see how the changes fit with the shared examples I''ve got so far... Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
David Chelimsky
2010-Aug-03 11:50 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 3, 2010, at 6:43 AM, Ashley Moran wrote:> > On Aug 03, 2010, at 12:22 pm, David Chelimsky wrote: > >> My inclination is to get this feature out with explicit non-support for 1.8.6, and then add support for 1.8.6 if we can get this to work. Working on that now - should be pushing some code (including Myron''s contribution) later today. > > Do you have everything in place to finish this off? Happy to help out if you want me to do any more coding on this, but it sounds like you''ve figured out a solution. In which case I''ll sit back and see how the changes fit with the shared examples I''ve got so far...Pushed: http://github.com/rspec/rspec-core/commit/84303616be1ac2f8126675488947b47f6945cebe http://github.com/rspec/rspec-core/commit/3cea7b8bea51766d632e20bcc9ef15c64b719ea1 Please do let me know if this works with what you''ve got. The issue of the evaluation order is still up for grabs, but this now supports params to shared groups in Ruby >= 1.8.7.> > Cheers > Ash > > -- > http://www.patchspace.co.uk/ > http://www.linkedin.com/in/ashleymoran > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Ashley Moran
2010-Aug-03 21:35 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 3 Aug 2010, at 12:50 PM, David Chelimsky wrote:> Pushed: > > http://github.com/rspec/rspec-core/commit/84303616be1ac2f8126675488947b47f6945cebe > http://github.com/rspec/rspec-core/commit/3cea7b8bea51766d632e20bcc9ef15c64b719ea1Awesomeness!> Please do let me know if this works with what you''ve got.In general, yes, this is a massive improvement! I''ve realised some things that never occurred to me before, though. Maybe you have some thoughts... I''ve put everything on a Gist[1] (which needs a few tweaks here and there, but I think it''s a reasonably example). Notes: * DomainLib is my holding module for everything I''ve extracted out of the project source. Anything inside that is generic, analogous to eg ActiveRecord (eg Entity <-> AR::Base) * I''ve only pasted the specs, and only the contract-based ones at that (the implementation is not very interesting, nor is the interaction spec). * I don''t like the word contract any more, at least not here. It needs a better name, probably something that would fit if you wrote a similar spec for ActiveRecord''s has_many. Some things I ran into: First, I found that you can''t use the block variables in local helper methods. Because Ruby methods aren''t closures, I''ve had to replace methods like: def entity_dot_new_collection_member(*args) entity.send(:"new_#{item_name}", *args) end with: define_method :entity_dot_new_collection_member do |*args| entity.send(:"new_#{item_name}", *args) end Not a big deal, but it''s not as readable as it was before. (Not that it was exactly large-print Winnie the Pooh to start with, given the abstract nature of the shared examples.) Second, you can''t refer to `described_class` in the descriptions. I don''t know why I though you''d be able to, but it would be nice if it worked :) (You can see the place where my failed attempt was, where I left <described_class>.) Finally, I realised something when I added another example. I should say though, that all this time, I was only using the shared examples with one collection on the entity, and I added another a few minutes ago just for fun, and it just worked... I like :) But it raised a point about things that are common to all shared examples, and parameters to individual uses. In my example case, `entity_class` and `entity` are relevant to both of the "collection" shared example groups, but `collection_name`, `item_name`, `class_name` are parameters to the shared examples individually. With the current setup, there''s no way to require that a host group provides eg `entity_class`. And also, if it''s defined as a `let` in the host, you can''t use it in the descriptions in the shared example group (which you couldn''t before, of course). So I think this solves 90% of the problems I had before, and is certainly a workable solution to the specs I''m trying to write. I''d love to hear your thoughts on the rest though.> The issue of the evaluation order is still up for grabs, but this now supports params to shared groups in Ruby >= 1.8.7.Well, I deliberately didn''t check what order you ended up using! Whatever it is works for me now, although I guess future experiments could change that... Cheers! Ash [1] http://gist.github.com/507140 -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
David Chelimsky
2010-Aug-04 00:05 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 3, 2010, at 4:35 PM, Ashley Moran wrote:> > On 3 Aug 2010, at 12:50 PM, David Chelimsky wrote: > >> Pushed: >> >> http://github.com/rspec/rspec-core/commit/84303616be1ac2f8126675488947b47f6945cebe >> http://github.com/rspec/rspec-core/commit/3cea7b8bea51766d632e20bcc9ef15c64b719ea1 > > Awesomeness! > > >> Please do let me know if this works with what you''ve got. > > In general, yes, this is a massive improvement! I''ve realised some things that never occurred to me before, though. Maybe you have some thoughts... > > I''ve put everything on a Gist[1] (which needs a few tweaks here and there, but I think it''s a reasonably example). Notes: > > * DomainLib is my holding module for everything I''ve extracted out of the project source. Anything inside that is generic, analogous to eg ActiveRecord (eg Entity <-> AR::Base) > > * I''ve only pasted the specs, and only the contract-based ones at that (the implementation is not very interesting, nor is the interaction spec). > > * I don''t like the word contract any more, at least not here. It needs a better name, probably something that would fit if you wrote a similar spec for ActiveRecord''s has_many.I actually like contract a lot. Maybe we''ll need alias_shared_examples_for_to :)> Some things I ran into: > > First, I found that you can''t use the block variables in local helper methods. Because Ruby methods aren''t closures, I''ve had to replace methods like: > > def entity_dot_new_collection_member(*args) > entity.send(:"new_#{item_name}", *args) > end > > with: > > define_method :entity_dot_new_collection_member do |*args| > entity.send(:"new_#{item_name}", *args) > end > > Not a big deal, but it''s not as readable as it was before. (Not that it was exactly large-print Winnie the Pooh to start with, given the abstract nature of the shared examples.)This is just Ruby. It bugged me for a while too, but mostly because I kept forgetting. Now I''m completely accustomed to it and def and define_method seem quite the same to me.> Second, you can''t refer to `described_class` in the descriptions. I don''t know why I though you''d be able to, but it would be nice if it worked :) (You can see the place where my failed attempt was, where I left <described_class>.)This was a mis-alignment between names in the group and its examples (example_group.describes == example.described_class), but is now fixed (you can refer to described_class in both cases): http://github.com/rspec/rspec-core/commit/b236a8d8927da108097fed7982d1450e4701939d> Finally, I realised something when I added another example. I should say though, that all this time, I was only using the shared examples with one collection on the entity, and I added another a few minutes ago just for fun, and it just worked... I like :) But it raised a point about things that are common to all shared examples, and parameters to individual uses. In my example case, `entity_class` and `entity` are relevant to both of the "collection" shared example groups, but `collection_name`, `item_name`, `class_name` are parameters to the shared examples individually. > > With the current setup, there''s no way to require that a host group provides eg `entity_class`.shared_examples_for "foo" do raise "gotta define entity_class" unless public_instance_methods.map{|m|m.to_s).include?("entity_class") end> And also, if it''s defined as a `let` in the host, you can''t use it in the descriptions in the shared example group (which you couldn''t before, of course).Right - the only thing available to descriptions is going to be the params you pass in.> So I think this solves 90% of the problems I had before, and is certainly a workable solution to the specs I''m trying to write. I''d love to hear your thoughts on the rest though. > > >> The issue of the evaluation order is still up for grabs, but this now supports params to shared groups in Ruby >= 1.8.7. > > Well, I deliberately didn''t check what order you ended up using! Whatever it is works for me now, although I guess future experiments could change that...Thanks for all the feedback! Cheers, David> > > Cheers! > Ash > > > [1] http://gist.github.com/507140 > > > -- > http://www.patchspace.co.uk/ > http://www.linkedin.com/in/ashleymoran > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Myron Marston
2010-Aug-04 06:55 UTC
[rspec-users] Evaluating shared example customisation block before shared block
Ashley: thanks for posting the example. It''s nice to see how this all fits together. Re: RSpec 2 for ruby 1.8.6: I don''t see RSpec 2 as being all that useful for Rails 2.x projects on ruby 1.8.6. However, it''s still very important for gems. I just converted one of my projects (VCR[1]) to RSpec 2, and VCR supports ruby 1.8.6, 1.8.7 and 1.9.1. If we remove ruby 1.8.6 support from RSpec 2, I''d have to migrate back to RSpec 1.x so that I can continue to run the spec suite on 1.8.6. I imagine there will be plenty of other libraries that will want to upgrade to using RSpec 2 after the final release, while still supporting 1.8.6. Good news: I messed around with module_exec some more, and I think I have a working implementation for 1.8.6[2]. This was complicated enough that I wanted to work on it in isolation from RSpec; hence the separate github project. We''ll probably want to re-organize it a bit before merging it in, if it''s deemed "good enough" to work for our needs. It has some specs that pass for module_exec on 1.8.7, and they pass on 1.8.6 with my implementation, too. There may be cases where it still doesn''t work quite right, though--feel free to fork, add specs, etc. Myron [1] http://github.com/myronmarston/vcr [2] http://github.com/myronmarston/module_exec
Ashley Moran
2010-Aug-04 08:22 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 4 Aug 2010, at 1:05 AM, David Chelimsky wrote:> I actually like contract a lot. Maybe we''ll need alias_shared_examples_for_to :)Haha, actually that gets +1 from me! Should I file a ticket? :) In general I like contract, I just wasn''t sure it was the right word for this usage of shared examples. Maybe I just need to reword the shared examples, to write something like this: it_satisfies_contract "container of", :children, :child, Child.name (Obviously, if I had an inflection library in place, you could drop the last 2 args)> This is just Ruby. It bugged me for a while too, but mostly because I kept forgetting. Now I''m completely accustomed to it and def and define_method seem quite the same to me.Maybe. Perhaps then Ruby needs a neater closure-based method syntax eg: foo = 1 defc my_method(bar) foo + bar end or some such...> This was a mis-alignment between names in the group and its examples (example_group.describes == example.described_class), but is now fixed (you can refer to described_class in both cases):http://github.com/rspec/rspec-core/commit/b236a8d8927da108097fed7982d1450e4701939dWorks for me! Ta :)> shared_examples_for "foo" do > raise "gotta define entity_class" unless public_instance_methods.map{|m|m.to_s).include?("entity_class") > endAye, I guess I''m just in love with DSLs... If I feel the need I might write a simple DSL and see if it''s worth it.>> And also, if it''s defined as a `let` in the host, you can''t use it in the descriptions in the shared example group (which you couldn''t before, of course). > > Right - the only thing available to descriptions is going to be the params you pass in.I have a feeling this will cause a misalignment, but maybe not. I''ll work through some more practical examples and see how it plays out. BTW any idea when the next beta will go out, so that this is in a released gem? I''ve got it working, but I had no luck using Bundler''s :path option so I ended up having to build and install the gems into my project gemset. That''s probably just a RubyGems/Bundler issue though. Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Ashley Moran
2010-Aug-04 08:30 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 4 Aug 2010, at 7:55 AM, Myron Marston wrote:> Ashley: thanks for posting the example. It''s nice to see how this all > fits together.Arguably it would have made more sense to post that example *before*, rather than expecting you all to read my mind :) I''m pleased with how it''s working out so far. I need to write a lot more of these to know though. I only got as far as this one example before deciding to shave the shared example yak before moving on.> Re: RSpec 2 for ruby 1.8.6: I don''t see RSpec 2 as being all that > useful for Rails 2.x projects on ruby 1.8.6. However, it''s still very > important for gems. I just converted one of my projects (VCR[1]) to > RSpec 2, and VCR supports ruby 1.8.6, 1.8.7 and 1.9.1. If we remove > ruby 1.8.6 support from RSpec 2, I''d have to migrate back to RSpec 1.x > so that I can continue to run the spec suite on 1.8.6. I imagine > there will be plenty of other libraries that will want to upgrade to > using RSpec 2 after the final release, while still supporting 1.8.6.Cool, if it''s possible to maintain 1.8.6 support in RSpec 2 then by all means do so. I wasn''t aware that such a large amount of code needed to run on 1.8.6, I assumed most had moved to 1.8.7. Doesn''t look like it takes too much to monkeypatch 1.8.6 up to spec anyway. Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Ashley Moran
2010-Aug-04 08:43 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On 4 Aug 2010, at 1:05 AM, David Chelimsky wrote:>One other thought I''ve had is keyword syntax. While currently I''m writing: it_satisfies_contract "[Entity] Collection:", :children, :child, Child.name I prefer keyword arguments, so I''d like to write: it_satisfies_contract "[Entity] Collection:", :children, item_name: "child", class_name: Child.name Currently that would mean rewriting the contract like this: contract "[Entity] Collection:" do |collection_name, options| # ... describe "#{collection_name}" do describe "Helper methods:" do describe "#new_#{options[:item_name]}, #get_#{options[:item_name]}" do # ... WDYT about RSpec automatically translating keyword options to methods? They''d need to be defined as singleton class methods and instance methods to have the same availability as block parameters. Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
David Chelimsky
2010-Aug-04 11:41 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 4, 2010, at 3:43 AM, Ashley Moran wrote:> > On 4 Aug 2010, at 1:05 AM, David Chelimsky wrote: > >> > > > One other thought I''ve had is keyword syntax. While currently I''m writing: > > it_satisfies_contract "[Entity] Collection:", :children, :child, Child.name > > I prefer keyword arguments, so I''d like to write: > > it_satisfies_contract "[Entity] Collection:", > :children, > item_name: "child", > class_name: Child.name > > Currently that would mean rewriting the contract like this: > > contract "[Entity] Collection:" do |collection_name, options| > > # ... > > describe "#{collection_name}" do > describe "Helper methods:" do > describe "#new_#{options[:item_name]}, #get_#{options[:item_name]}" do > > # ... > > WDYT about RSpec automatically translating keyword options to methods?What happens if the shared spec author really wants it to just be a hash? Do you think that''s a valid use case?> They''d need to be defined as singleton class methods and instance methods to have the same availability as block parameters. > > Ash > > -- > http://www.patchspace.co.uk/ > http://www.linkedin.com/in/ashleymoran > > > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
David Chelimsky
2010-Aug-04 11:44 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 4, 2010, at 1:55 AM, Myron Marston wrote:> Ashley: thanks for posting the example. It''s nice to see how this all > fits together. > > Re: RSpec 2 for ruby 1.8.6: I don''t see RSpec 2 as being all that > useful for Rails 2.x projects on ruby 1.8.6. However, it''s still very > important for gems. I just converted one of my projects (VCR[1]) to > RSpec 2, and VCR supports ruby 1.8.6, 1.8.7 and 1.9.1. If we remove > ruby 1.8.6 support from RSpec 2, I''d have to migrate back to RSpec 1.x > so that I can continue to run the spec suite on 1.8.6. I imagine > there will be plenty of other libraries that will want to upgrade to > using RSpec 2 after the final release, while still supporting 1.8.6. > > Good news: I messed around with module_exec some more, and I think I > have a working implementation for 1.8.6[2]. This was complicated > enough that I wanted to work on it in isolation from RSpec; hence the > separate github project. We''ll probably want to re-organize it a bit > before merging it in, if it''s deemed "good enough" to work for our > needs. It has some specs that pass for module_exec on 1.8.7, and they > pass on 1.8.6 with my implementation, too. There may be cases where > it still doesn''t work quite right, though--feel free to fork, add > specs, etc.Hey Myron - I think what you have is perfectly fine. The only issue I ran into was that of defining instance methods, and your solution seems sound. I wouldn''t even bother to undef those methods. We''re not putting module_exec in as an API. In fact, in rspec, I think we should change the names of module_exec and instance_exec to something rspec-specific so that users don''t rely on our implementation for other purposes. Something like: def module_eval_with_args(*args, &block) if respond_to?(:module_exec) module_exec(*args, &block) else # custom solution end end At that point, as long as all the shared group specs are passing, we''re good. Make sense?> > Myron > > [1] http://github.com/myronmarston/vcr > [2] http://github.com/myronmarston/module_exec > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users
Myron Marston
2010-Aug-04 18:35 UTC
[rspec-users] Evaluating shared example customisation block before shared block
> I wouldn''t even bother to undef those methods.If we don''t undef the methods, then the semantics of the #module_eval_with_args (or whatever we call it) will be different on 1.8.6 and other versions. On 1.8.6, a method definition in the block would define both an instance method _and_ a class method. Someone could write a spec against 1.8.6, and accidentally call the class method, not realizing they''ve done this, and the spec wouldn''t work on 1.8.7 and above since the class method won''t be there. So I think the undefs are important, and I don''t think it adds too much complexity.> Something like: > > def module_eval_with_args(*args, &block) > ? if respond_to?(:module_exec) > ? ? module_exec(*args, &block) > ? else > ? ? # custom solution > ? end > end > > At that point, as long as all the shared group specs are passing, we''re good. Make sense?Makes total sense. I''ll work on porting this to RSpec, and open an github issue with a link to the commits when I''m done. Thanks!
David Chelimsky
2010-Aug-05 03:28 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 4, 2010, at 1:35 PM, Myron Marston wrote:>> I wouldn''t even bother to undef those methods. > > If we don''t undef the methods, then the semantics of the > #module_eval_with_args (or whatever we call it) will be different on > 1.8.6 and other versions. On 1.8.6, a method definition in the block > would define both an instance method _and_ a class method. Someone > could write a spec against 1.8.6, and accidentally call the class > method, not realizing they''ve done this, and the spec wouldn''t work on > 1.8.7 and above since the class method won''t be there. So I think the > undefs are important, and I don''t think it adds too much complexity. > >> Something like: >> >> def module_eval_with_args(*args, &block) >> if respond_to?(:module_exec) >> module_exec(*args, &block) >> else >> # custom solution >> end >> end >> >> At that point, as long as all the shared group specs are passing, we''re good. Make sense? > > Makes total sense. I''ll work on porting this to RSpec, and open an > github issue with a link to the commits when I''m done.FYI - to those paying attention - I merged Myron''s changes with support for parameterized shared groups even in 1.8.6. At this point, the customization block is still being eval''d after the shared block, and I''m fairly well convinced this is the right thing, in combination with params to the block. Next release will FINALLY have parameterized shared groups. Sweet! Cheers, David
Ashley Moran
2010-Aug-06 08:16 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 04, 2010, at 12:41 pm, David Chelimsky wrote:> What happens if the shared spec author really wants it to just be a hash? Do you think that''s a valid use case?It could get in the way, then, I guess. You''d always have the original hash parameter if you wanted to use the method, but I guess it could cause trouble if you did this, or similar: shared_examples_for "a foo container" do |foo, options = {}| it "has a #{foo}" do; end end describe Bar do it_should_behave_like "a foo container", 1, foo: 2 end I''ll probably play with this idea in my own code. There''s definitely no need worry about it now, being able to pass arguments to shared example groups is 90% of the win for me. Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Ashley Moran
2010-Aug-06 08:18 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 05, 2010, at 4:28 am, David Chelimsky wrote:> At this point, the customization block is still being eval''d after the shared block, and I''m fairly well convinced this is the right thing, in combination with params to the block.I don''t think it makes any different any more, at least not to me. The only thing you can''t do is use class methods in shared example descriptions, but you don''t need to do that any more now anyway.> Next release will FINALLY have parameterized shared groups. Sweet!Brilliant :-) What''s the current release plan? Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
David Chelimsky
2010-Aug-06 10:58 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 6, 2010, at 3:18 AM, Ashley Moran wrote:> > On Aug 05, 2010, at 4:28 am, David Chelimsky wrote: > >> At this point, the customization block is still being eval''d after the shared block, and I''m fairly well convinced this is the right thing, in combination with params to the block. > > I don''t think it makes any different any more, at least not to me. The only thing you can''t do is use class methods in shared example descriptions, but you don''t need to do that any more now anyway. > >> Next release will FINALLY have parameterized shared groups. Sweet! > > Brilliant :-) What''s the current release plan?Barring the unforeseen, I''ll knock out beta.20 this weekend. David> > Cheers > Ash
Ashley Moran
2010-Aug-06 11:00 UTC
[rspec-users] Evaluating shared example customisation block before shared block
On Aug 06, 2010, at 11:58 am, David Chelimsky wrote:> Barring the unforeseen, I''ll knock out beta.20 this weekend.Cool, ta! Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran