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
> >
>