Hello, Does anyone have an explanation for why the following code results in the error that follows? I also would value any criticisms of my approach (i.e., defining it_should_populate_collections). Thanks -Chris #Code def it_should_populate_collections(condition=nil) it "should populate any necessary collections #{condition}" do [*@collections_to_populate].each do |collection| collection_model = collection.to_s.classify.constantize collection_model.should_receive(:find).with(:all).and_return("the #{collection}") end yield [*@collections_to_populate].each do |collection| assigns[collection].should == "the #{collection}" end end end describe "Foo" do before(:all) do @collections_to_populate = :bars end before(:each) do @obj = Foo.new end it_should_populate_collections "on a save failure" do @obj.should_receive(:save) post @action end end #Error undefined method `post'' for #<Spec::DSL::EvalModule:0xb6e7b2e0>
On 5/15/07, Chris Hoffman <chris.c.hoffman at gmail.com> wrote:> Hello, > > Does anyone have an explanation for why the following code results in > the error that follows? I also would value any criticisms of my > approach (i.e., defining it_should_populate_collections). Thanks > > -Chris > > #Code > > def it_should_populate_collections(condition=nil) > it "should populate any necessary collections #{condition}" do > [*@collections_to_populate].each do |collection| > collection_model = collection.to_s.classify.constantize > collection_model.should_receive(:find).with(:all).and_return("the > #{collection}") > end > yield > [*@collections_to_populate].each do |collection| > assigns[collection].should == "the #{collection}" > end > end > end > > describe "Foo" do > before(:all) do > @collections_to_populate = :bars > end > > before(:each) do > @obj = Foo.new > end > > it_should_populate_collections "on a save failure" do > @obj.should_receive(:save) > post @action > end > end > > #Error > > undefined method `post'' for #<Spec::DSL::EvalModule:0xb6e7b2e0>post is only available in controller specs, identified by living in spec/controllers or by saying: describe "Foo", :behaviour_type => :controller do My guess is that you''re doing this in spec/models, but that''s just a guess (please include complete error messages instead of just one line when you post questions like this). As for the approach: 1. I''d recommend only using before(:all) for expensive operations like initializing databases. In this example, there is no benefit to using before(:all). Not even a runtime benefit, because any instance vars that you set up get copied around which is no less expensive than simply re-creating them before(:each) example. Using before(:all) and before(:each) together just makes things confusing. 2. "it_should_populate_collections" speaks very nicely from a documentation standpoint, but from a failure isolation standpoint (i.e. understanding quickly why something fails when it does) I think it''s a bit problematic. There''s just no way to understand what''s going on in the example without looking at the definition of it_should_populate_collections. It''s very clever, but takes attention away from where it should be. The fact that it yields back to the current block makes it even more confusing. I''d stay away from that in specs. In general, I''d encourage you to use custom matchers rather than adding "it_blah" methods (i.e. @model.should populate_collections(:a, :b) ). I think that will make it much easier for other people to look at your specs and understand them from a documentation perspective, and a failure isolation perspective. 2 cents Cheers, David
Sorry about being incomplete; here is the whole error message: NoMethodError in ''PagesController#new should populate any necessary collections '' undefined method `get'' for #<Spec::DSL::EvalModule:0xb6f2397c> ./spec/controllers/../controllers/crud_spec_helper.rb:88: ./spec/controllers/../controllers/crud_spec_helper.rb:17:in `it_should_populate_collections'' script/spec:4: On 5/15/07, David Chelimsky <dchelimsky at gmail.com> wrote:> On 5/15/07, Chris Hoffman <chris.c.hoffman at gmail.com> wrote: > > Hello, > > > > Does anyone have an explanation for why the following code results in > > the error that follows? I also would value any criticisms of my > > approach (i.e., defining it_should_populate_collections). Thanks > > > > -Chris > > > > #Code > > > > def it_should_populate_collections(condition=nil) > > it "should populate any necessary collections #{condition}" do > > [*@collections_to_populate].each do |collection| > > collection_model = collection.to_s.classify.constantize > > collection_model.should_receive(:find).with(:all).and_return("the > > #{collection}") > > end > > yield > > [*@collections_to_populate].each do |collection| > > assigns[collection].should == "the #{collection}" > > end > > end > > end > > > > describe "Foo" do > > before(:all) do > > @collections_to_populate = :bars > > end > > > > before(:each) do > > @obj = Foo.new > > end > > > > it_should_populate_collections "on a save failure" do > > @obj.should_receive(:save) > > post @action > > end > > end > > > > #Error > > > > undefined method `post'' for #<Spec::DSL::EvalModule:0xb6e7b2e0> > > post is only available in controller specs, identified by living in > spec/controllers or by saying: > > describe "Foo", :behaviour_type => :controller do > > My guess is that you''re doing this in spec/models, but that''s just a > guess (please include complete error messages instead of just one line > when you post questions like this). > > As for the approach: > > 1. I''d recommend only using before(:all) for expensive operations like > initializing databases. In this example, there is no benefit to using > before(:all). Not even a runtime benefit, because any instance vars > that you set up get copied around which is no less expensive than > simply re-creating them before(:each) example. Using before(:all) and > before(:each) together just makes things confusing. > > 2. "it_should_populate_collections" speaks very nicely from a > documentation standpoint, but from a failure isolation standpoint > (i.e. understanding quickly why something fails when it does) I think > it''s a bit problematic. There''s just no way to understand what''s going > on in the example without looking at the definition of > it_should_populate_collections. It''s very clever, but takes attention > away from where it should be. The fact that it yields back to the > current block makes it even more confusing. I''d stay away from that in > specs. > > In general, I''d encourage you to use custom matchers rather than > adding "it_blah" methods (i.e. @model.should populate_collections(:a, > :b) ). I think that will make it much easier for other people to look > at your specs and understand them from a documentation perspective, > and a failure isolation perspective. > > 2 cents > > Cheers, > David > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
Okay, I killed two birds with one stone, I hope. I saw what you meant by my overly clever approach not making much sense to someone who is trying to read the code. I have made a custom matcher instead. I would appreciate any more comments you may have. I have made every controller spec define its necessary collections, which is what feeds into the matcher. I wouldn''t mind a more elegant approach, such as some keyword in the model association definition that declares it to be assigned by the model. For example: class Foo < AR::Base belongs_to :bar, :assignable end Anyway, here is what I have so far for the custom matcher. module CustomCRUDMatchers class BePopulatedWith def initialize(collections_to_populate=nil) @collections_to_populate = [*collections_to_populate] end def matches?(assigns) @assigns = assigns #not sure why I need this to get inside the following block @collections_to_populate.each do |collection| unless @assigns[collection].eql? "the #{collection}" @failed_collection = collection return false end end end def failure_message "expected #{@failed_collection} to be populated with ''the #{@failed_collection}'', got #{@assigns[@failed_collection].inspect} instead" end def negative_failure_message "expected #{@failed_collection} not to be populated, got #{@assigns[@failed_collection].inspect} instead" end end def be_populated_with(collections) BePopulatedWith.new(collections) end end describe "Foo#new" do include CustomCRUDMatchers before(:each) do @action = "new" @collections_to_populate = :bars end it "should render the ''#@action'' template" do get @action response.should render_template(@action) end it "should populate collections relevant to the form" do get @action assigns.should be_populated_with(@collections_to_populate) end end On 5/15/07, Chris Hoffman <chris.c.hoffman at gmail.com> wrote:> Sorry about being incomplete; here is the whole error message: > > NoMethodError in ''PagesController#new should populate any necessary > collections '' > undefined method `get'' for #<Spec::DSL::EvalModule:0xb6f2397c> > ./spec/controllers/../controllers/crud_spec_helper.rb:88: > ./spec/controllers/../controllers/crud_spec_helper.rb:17:in > `it_should_populate_collections'' > script/spec:4: > > On 5/15/07, David Chelimsky <dchelimsky at gmail.com> wrote: > > On 5/15/07, Chris Hoffman <chris.c.hoffman at gmail.com> wrote: > > > Hello, > > > > > > Does anyone have an explanation for why the following code results in > > > the error that follows? I also would value any criticisms of my > > > approach (i.e., defining it_should_populate_collections). Thanks > > > > > > -Chris > > > > > > #Code > > > > > > def it_should_populate_collections(condition=nil) > > > it "should populate any necessary collections #{condition}" do > > > [*@collections_to_populate].each do |collection| > > > collection_model = collection.to_s.classify.constantize > > > collection_model.should_receive(:find).with(:all).and_return("the > > > #{collection}") > > > end > > > yield > > > [*@collections_to_populate].each do |collection| > > > assigns[collection].should == "the #{collection}" > > > end > > > end > > > end > > > > > > describe "Foo" do > > > before(:all) do > > > @collections_to_populate = :bars > > > end > > > > > > before(:each) do > > > @obj = Foo.new > > > end > > > > > > it_should_populate_collections "on a save failure" do > > > @obj.should_receive(:save) > > > post @action > > > end > > > end > > > > > > #Error > > > > > > undefined method `post'' for #<Spec::DSL::EvalModule:0xb6e7b2e0> > > > > post is only available in controller specs, identified by living in > > spec/controllers or by saying: > > > > describe "Foo", :behaviour_type => :controller do > > > > My guess is that you''re doing this in spec/models, but that''s just a > > guess (please include complete error messages instead of just one line > > when you post questions like this). > > > > As for the approach: > > > > 1. I''d recommend only using before(:all) for expensive operations like > > initializing databases. In this example, there is no benefit to using > > before(:all). Not even a runtime benefit, because any instance vars > > that you set up get copied around which is no less expensive than > > simply re-creating them before(:each) example. Using before(:all) and > > before(:each) together just makes things confusing. > > > > 2. "it_should_populate_collections" speaks very nicely from a > > documentation standpoint, but from a failure isolation standpoint > > (i.e. understanding quickly why something fails when it does) I think > > it''s a bit problematic. There''s just no way to understand what''s going > > on in the example without looking at the definition of > > it_should_populate_collections. It''s very clever, but takes attention > > away from where it should be. The fact that it yields back to the > > current block makes it even more confusing. I''d stay away from that in > > specs. > > > > In general, I''d encourage you to use custom matchers rather than > > adding "it_blah" methods (i.e. @model.should populate_collections(:a, > > :b) ). I think that will make it much easier for other people to look > > at your specs and understand them from a documentation perspective, > > and a failure isolation perspective. > > > > 2 cents > > > > Cheers, > > David > > _______________________________________________ > > rspec-users mailing list > > rspec-users at rubyforge.org > > http://rubyforge.org/mailman/listinfo/rspec-users > > >