How can I use rspec to test routes that are being rescued and redirected? - ruby-on-rails-3

I was trying to write some quick routing tests for a simple api so I wrote:
it "delete" do
delete("/api/notifications/:id").should_not be_routable
end
But I received:
Failure/Error: delete("/api/notifications/:id").should_not be_routable
expected {:delete=>"/api/notifications/:id"} not to be routable, but it routes to {:controller=>"application", :action=>"rescue404", :a=>"api/notifications/:id"}
I quickly realized I was rescuing from 404, so pretty much everything is routable.
unless Rails.env.development?
rescue_from NotFound, :with => :rescue404
rescue_from ActiveRecord::RecordNotFound, :with => :rescue404
rescue_from ActionController::RoutingError, :with => :rescue404
end
def rescue404
respond_to do |format|
format.json { render :text => 'Something went wrong. Record not found or url is incorrect.\n' }
format.xml { render :text => 'Something went wrong. Record not found or url is incorrect.\n' }
format.html { redirect_to root_url, :alert => 'That page does not exist, sorry bro!' }
end
end
That leaves me with this for a test:
it "delete" do
delete("/api/notifications/:id").should route_to("application#rescue404", :a => "api/notifications/:id")
end
Writing this way is error prone to me as I'm constantly getting the ':a =>' wrong. Is there any way I can test if an exception is being rescued?
This works:
it "delete" do
delete("/api/notifications/:id").should raise_error()
end
...but what error should I be checking for? Or should I just leave it at that?

You could change your rescues. Change:
unless Rails.env.development?
to:
if Rails.env.production?
That should leave the test env out of your rescue404 behavior.

Related

respond_with location ignored on GET request

At each step in my checkout process, an order is updated via a PUT request. However, one of the states has a form that submits to a third party which redirects back to my site, calling the update method with GET (no control over this).
Why does my respond_with code appear to be totally ignored and I get a Missing Template checkout/update error? It should be hitting #edit.
CheckoutController.rb
before_filter :load_order
def update
if #order.update_attributes(params[:order])
#order.next
end
respond_with(#order, :location => checkout_state_url(#order.state))
end
routes.rb
match '/checkout/update/:state' => 'checkout#update', :as => :update_checkout
match '/checkout/:state' => 'checkout#edit', :as => :checkout_state
match '/checkout' => 'checkout#edit', :state => 'client_details', :as => :checkout
It looks like respond_with does different things depending upon the HTTP verb and whether the resource has errors. See here and here.
The following code worked for me:
def update
if #order.update_attributes(params[:order]) && #order.next
respond_with(#order) { |format| format.html { redirect_to checkout_state_url(#order.state) } }
else
respond_with(#order) { |format| format.html { render :edit } }
end
end

Exception notifier - how to display own error pages?

I use exception_notification gem for handling an errors in an app.
My ApplicationController looks like this:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception,
:with => :render_error
rescue_from ActiveRecord::RecordNotFound,
:with => :render_not_found
rescue_from ActionController::RoutingError,
:with => :render_not_found
rescue_from ActionController::UnknownController,
:with => :render_not_found
rescue_from ActionController::UnknownAction,
:with => :render_not_found
end
def render_not_found(exception)
ExceptionNotifier::Notifier
.exception_notification(request.env, exception)
.deliver
render :template => "/errors/404.html.erb",
:layout => 'errors.html.erb'
return
end
def render_error(exception)
ExceptionNotifier::Notifier
.exception_notification(request.env, exception)
.deliver
render :template => "/errors/500.html.erb",
:layout => 'errors.html.erb'
return
end
In /config/enviroments/productions.rg in the end of the file I have:
config.middleware.use ExceptionNotifier,
:email_prefix => "[MY APP| Error Report] ",
:sender_address => %{"MY APP" <err#my-app.com>},
:exception_recipients => 'my_email#gmail.com'
end
when I get the error on the app - eg. Article.find(not-existing-ID), I'll get the standard error page (ERROR 500) from /public/500.html and not from the file specified in application controller... How is that possible? Past hours I tried to find the problem, but I still don't know the issue.
This works for me - from application_controller.rb:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :render_500
rescue_from ActionController::RoutingError, with: :render_404
rescue_from ActionController::UnknownController, with: :render_404
rescue_from ActionController::UnknownAction, with: :render_404
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
and
private
def render_404(exception)
ExceptionNotifier::Notifier.exception_notification(request.env, exception,
:data => {:User => current_user.full_name, :Email => current_user.email, :UserID => current_user.id}).deliver
#not_found_path = exception.message
respond_to do |format|
format.html { render template: 'pages/404', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404}
end
end
def render_500(exception)
ExceptionNotifier::Notifier.exception_notification(request.env, exception,
:data => {:User => current_user.full_name, :Email => current_user.email, :UserID => current_user.id}).deliver
#error = exception
respond_to do |format|
format.html { render template: 'pages/500', layout: 'layouts/application', status: 500 }
format.all { render nothing: true, status: 500}
end
end
Note: I have my custom error pages in /app/views/pages. Called 500.html.haml and 404.html.haml
I also have this in my routes.rb (note a hash after 'pages rather than a forward-slash):
unless Rails.application.config.consider_all_requests_local
match '*not_found', to: 'pages#404'
end
PS. See updated instructions here: http://ramblinglabs.com/blog/2012/01/rails-3-1-adding-custom-404-and-500-error-pages
Hi this works for me try it!
In the application_controller.rb you can add these codes:
if Rails.env.production?
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, with: :render_500
rescue_from ActionController::RoutingError, with: :render_404
rescue_from ActionController::UnknownController, with: :render_404
rescue_from ActionController::UnknownAction, with: :render_404
rescue_from ActiveRecord::RecordNotFound, with: :render_404
end
end
This codes checks if you are running your rails app using production mode and catches different rescue errors.
In the same controller "application_controller.rb" add these codes:
def render_404(exception)
#not_found_path = exception.message
respond_to do |format|
format.html { render template: 'errors/not_found', layout: 'layouts/application', status: 404 }
format.all { render nothing: true, status: 404 }
end
end
def render_500(exception)
logger.info exception.backtrace.join("\n")
respond_to do |format|
format.html { render template: 'errors/internal_server_error', layout: 'layouts/application', status: 500 }
format.all { render nothing: true, status: 500}
end
end
These methods will specify the page you want to display when your app catches an error,
then create a controller named: errors_controller.rb
then in your app/view/errors
create a file named:
internal_server_error.html.erb
and
not_found.html.erb
and in your routes.rb
add these codes:
match '/internal_server_error', :to => 'errors#internal_server_error'
match '/not_found', :to => 'errors#not_found'

Rails 3.1 respond_to & render_403 problem

I'm not sure what it is. I've just upgraded to Rails 3.1 from 3.0.9 and here's what I get in my specs:
PeopleController edit action should require owner
Failure/Error: response.should render_template("/public/403.html")
expecting <"/public/403.html"> but rendering with <"search/_search_menu_item">
This is all over my specs in various controllers. I also have this code in my AppController:
def render_403
respond_to do |format|
format.html { render :file => "#{Rails.root}/public/403.html", :status => 403, :layout => false }
format.json { render :json => { :error => true, :message => "Error 403, you don't have permissions for this operation." } }
end
end
And this in PeopleController:
def edit
render_403 unless #person.account.id == current_account.id
end
I'm certain that format.html block gets executed (checked it). However the spec expectation fails. Wonder what is going on here.
(search/_search_menu_item is a partial that gets included onto every page, which basically means that the app layout gets rendered here instead.)
Update: I've replaced render_403 in #edit with render(:file => "#{Rails.root}/public/403.html", :status => 403, :layout => false) to see what happens - got the same result.
Ok, figured it out. Possibly not a Rails problem. At least the problem appears only when running the specs.
I've been checking if 403 pages are rendered with this:
response.should render_template("public/403.html")
Doesn't work no more. Replacing it with
response.status.should == 403
fixed the issue.

Rails, development env and error pages

I've made a simple app and I wanted to test pages for 404, 500 etc. http errors. I've changed config.consider_all_requests_local to false in my enviroments/development.rb but I've still got some problems so I would like to ask you a few questions...
If I type in my bowser something inappropriate like http://localhost:3000/products/dfgdgdgdgfd I still see the old "Unknown action" site. However if I type local ip adress of my computer for ex. http://192.168.1.106:3000/products/dfgdgdgdgfd I can see the 404 error page from public folder. Why is that happening?
I know that if I deploy my little project somewhere than my app will use the production mode and if any error would occure the 404 or 500 page will show up. But what if I want to make those error pages more dynamic ( for ex. rendering error message while using a layout with a list of popular products) or simply redirecting them to the main page?
2.1. The first solution that I found was to use rescue_from method in application controller:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, :with => :render_error
rescue_from ActiveRecord::RecordNotFound, :with => :render_not_found
rescue_from AbstractController::ActionNotFound, :with => :render_not_found
rescue_from ActionController::RoutingError, :with => :render_not_found
rescue_from ActionController::UnknownController, :with => :render_not_found
rescue_from ActionController::UnknownAction, :with => :render_not_found
end
.
.
.
private
def render_error exception
Rails.logger.error(exception)
redirect_to root_path
#or
# render :controller=>'errors', :action=>'error_500', :status=>500
end
def render_not_found exception
Rails.logger.error(exception)
redirect_to root_path
#or
# render :controller=>'errors', :action=>'error_404', :status=>404
end
... but that code didn't work at any case.
2.2. The second solution was to place match "*path" , :to => "products#show", :id=>1 (that's the example main page in my silly app) or match "*path" , :to => "errors#error_404", :id=>1 at the end of the routes.rb file. That code works only for typos like http://192.168.1.106:3000/dfgdgdgdgfd because if I try http://192.168.1.106:3000/products/dfgdgdgdgfd (the controller exists but the action is not found) I still got the 404 page.
I've played a bit trying sth like match "*path/*act" , :to => "products#show", :id=>1 or match ":controller(/*act)" , :to => "products#show", :id=>8 but that didn't work either...
2.3. The third solution was to make controller for errors and a file in initializers folder with this code:
# initializers/error_pages.rb
module ActionDispatch
class ShowExceptions
protected
def rescue_action_in_public(exception)
status = status_code(exception).to_s
template = ActionView::Base.new(["#{Rails.root}/app/views"])
if ["404"].include?(status)
file = "/errors/404.html.erb"
else
file = "/errors/500.html.erb"
end
body = template.render(:file => file)
render(status, body)
end
end
end
That was quite useful because it would let me to render dynamic erb files but.. it's not rendering any layout. I've tried to change body = template.render(:file => file) to body = template.render(:partial => file, :layout => "layouts/application") but it was only cousing errors.
I know that I'm doing sth wrong and I belive that there is a working solution for those error pages so I hope that you can help...
Cheers.
In your application controller you need to override this method:
def method_missing(m, *args, &block)
Rails.logger.error(m)
redirect_to :controller=>"errors", :action=>"error_404"
# or render/redirect_to somewhere else
end
and then you have to combine it with this code:
unless Rails.application.config.consider_all_requests_local
rescue_from Exception, :with => :method_missing
rescue_from ActiveRecord::RecordNotFound, :with => :method_missing
rescue_from AbstractController::ActionNotFound, :with => :method_missing
rescue_from ActionController::RoutingError, :with => :method_missing
rescue_from ActionController::UnknownController, :with => :method_missing
rescue_from ActionController::UnknownAction, :with => :method_missing
end

RSpec Newbie: "Update attributes => false" not being recognised

Just starting out with RSpec. Everything is going smoothly, except for one spec with nested controllers.
I'm trying to ensure that when a 'comment' resource (nested under 'post') is updated with invalid parameters, it renders the 'edit' template. I'm struggling to get rspec to recognise the :update_attributes => false trigger. If anyone has any suggestions, they'd be very appreciated. Attempted code below:
def mock_comment(stubs={})
stubs[:post] = return_post
stubs[:user] = return_user
#mock_comment ||= mock_model(Comment, stubs).as_null_object
end
describe "with invalid paramters" dog
it "re-renders the 'edit' template" do
Comment.stub(:find).with("12") { mock_comment(:update_attributes => false) }
put :update, :post_id => mock_comment.post.id, :id => "12"
response.should render_template("edit")
end
end
And the controller:
def update
#comment = Comment.find(params[:id])
respond_to do |format|
if #comment.update_attributes(params[:comment])
flash[:notice] = 'Post successfully updated'
format.html { redirect_to(#comment.post) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #comment.errors, :status => :unprocessable_entity }
end
end
end
And finally, the error:
Failure/Error: response.should render_template("edit")
expecting <"edit"> but rendering with <"">.
Expected block to return true value.
This is quite an interesting problem. A quick fix is to simply replace the block form of Comment.stub:
Comment.stub(:find).with("12") { mock_comment(:update_attributes => false) }
with an explicit and_return:
Comment.stub(:find).with("12").\
and_return(mock_comment(:update_attributes => false))
As to why these two forms should produce different results, that's a bit of a head-scratcher. If you play around with the first form you'll see that the mock is actually returning self instead of false when the stubbed method is called. That's tells us it hasn't stubbed the method (since it's specified as a null object).
The answer is that when passing in a block, the block is only executed when the stubbed method is called, not when the stub is defined. So when using the block form, the following call:
put :update, :post_id => mock_comment.post.id, :id => "12"
is executing mock_comment for the first time. Since :update_attributes => false is not being passed in, the method is not stubbed, and the mock is returned rather than false. When the block invokes mock_comment it returns #mock_comment, which doesn't have the stub.
Contrariwise, using the explicit form of and_return invokes mock_comment immediately. It would probably be better to use the instance variable instead of calling the method each time to make the intent clearer:
it "re-renders the 'edit' template" do
mock_comment(:update_attributes => false)
Comment.stub(:find).with("12") { #mock_comment }
put :update, :post_id => #mock_comment.post.id, :id => "12"
response.should render_template("edit")
end