Understanding Rails 3's respond_with
Utilizing ActionController's new respond_with
method...how does it determine what to render when action (save) is successful and when it's not?
I ask because I'm trying to get a scaffold generated spec (included below) to pass, if only so that I can understand it. The app is working fine but, oddly, it appears to be rendering /carriers
(at least that's what the browser's URL says) when a validation fails. Yet, the spec is expecting "new"
(and so am I, for that matter) but instead is receiving <"">
. If I change the spec to expect ""
it still fails.
When it renders /carriers
that page shows the error_messages next to the fields that failed validation as one would expect.
Can anyone familiar with respond_with
see what's happening here?
#carrier.rb
validates :name, :presence => true
#carriers_controller.rb
class CarriersController < ApplicationController
respond_to :html, :json
...
def new
respond_with(@carrier = Carrier.new)
end
def create
@carrier = Carrier.new(params[:carrier])
flash[:success] = 'Carrier was successfully created.' if @carrier.save
respond_with(@carrier)
end
Spec that's failing:
#carriers_controller_spec.rb
require 'spec_helper'
describe CarriersController do
def mock_carrier(stubs={})
(@mock_carrier ||= mock_model(Carrier).as_null_object).tap do |carrier|
carrier.stub(stubs) unless stubs.empty?
end
end
describe "POST create" do
describe "with invalid params" do
it "re-renders the 'new' template" do
Carrier.stub(:new) { mock_carrier(:save => false) }
post :create, :carrier => {}
response.should render_template("new")
end
end
end
end
with this error:
1) CarriersC开发者_如何学JAVAontroller POST create with invalid params re-renders the 'new' template
Failure/Error: response.should render_template("new")
expecting <"new"> but rendering with <"">.
Expected block to return true value.
# (eval):2:in `assert_block'
# ./spec/controllers/carriers_controller_spec.rb:81:in `block (4 levels) in <top (required)>'
tl:dr
Add an error hash to the mock:
Carrier.stub(:new) { mock_carrier(:save => false,
:errors => { :anything => "any value (even nil)" })}
This will trigger the desired behavior in respond_with
.
What is going on here
Add this after the post :create
response.code.should == "200"
It fails with expected: "200", got: "302"
. So it is redirecting instead of rendering the new template when it shouldn't. Where is it going? Give it a path we know will fail:
response.should redirect_to("/")
Now it fails with Expected response to be a redirect to <http://test.host/> but was a redirect to <http://test.host/carriers/1001>
The spec is supposed to pass by rendering the new
template, which is the normal course of events after the save
on the mock Carrier object returns false. Instead respond_with
ends up redirecting to show_carrier_path
. Which is just plain wrong. But why?
After some digging in the source code, it seems that the controller tries to render 'carriers/create'. There is no such template, so an exception is raised. The rescue block determines the request is a POST and there is nothing in the error hash, upon which the controller redirects to the default resource, which is the mock Carrier
.
That is puzzling, since the controller should not assume there is a valid model instance. This is a create
after all. At this point I can only surmise that the test environment is somehow taking shortcuts.
So the workaround is to provide a fake error hash. Normally something would be in the hash after save
fails, so that kinda makes sense.
精彩评论