Rails Custom Error Page Working and Not Working - ruby-on-rails-4

ruby-2.3.7 Rails 5.1.4
I'm trying to learn about error pages in Rails apps: how to create them and how they get called/triggered. I've read quite a few posts and blogs about this. I've found several that use the same approach. It seems simple enough, so I tried to implement it.
When I go directly to my new 404 or 500 page: ...myapp/404 or ...myapp/500 I get the correct custom 404/500 page. But when I trigger a different 500 error, I do not. I.e: ...myapp.employee/99999 throws an error because there is no such employee:
500 Internal Server Error
If you are the administrator of this website, then please read this web application's log file and/or the web server's log file to find out what went wrong
How do I get errors to go to my new error pages?
Code:
config/environments/development.rb:
-------
# see error pages in dev site
config.consider_all_requests_local = false # true
config/application.rb:
-------
# added for error pages
config.exceptions_app = self.routes
config/routes.rb
------
get "/404", to: "errors#not_found", :via => :all
get "/500", to: "errors#internal_server_error", :via => :all
controllers/errors_controller.rb
-----
class ErrorsController < ApplicationController
def not_found
respond_to do |format|
format.html { render status: 404 }
end
end
def internal_server_error
respond_to do |format|
format.html { render status: 500 }
end
end
end
---> these work so I wont paste the content here.
app/views/errors/not_found.html.erb
app/views/errors/internal_server_error.html.erb
Is this the right approach? If Yes, what am I missing or not understanding.
Thanks for any help.

You need to add these in your top level (application) controller and rescue accordingly. Some sample code from a project I'm working on:
rescue_from ActiveRecord::RecordNotFound do |e|
puts "#{e.inspect}, PARAMS: #{params.inspect}" if Config[:verbose_tests]
respond_with_404
end
rescue_from ActionController::RoutingError do |e|
puts "#{e.inspect}, PARAMS: #{params.inspect}" if Config[:verbose_tests]
respond_with_404
end
rescue_from Pundit::NotAuthorizedError do
respond_with_403
end
In your case, this would be something like:
// app/controllers/application_controller.rb
rescue_from ActiveRecord::RecordNotFound do
not_found
end
rescue_from ActionController::RoutingError do
not_found
end
rescue_from ActionController::StandardError do
internal_server_error
end
Make sure you have access to your internal_server_error and not_found methods inside your application controller, you can even define them there, and then when the error is thrown your app should respond accordingly.

Related

Cannot find existing route

I have this route
get 'versions' => 'main#versions', formats: [:xml]
In my MainController, I have an action versions thus:
class MainController < ApplicationController
...
def versions
render formats: [:xml]
end
end
and under app/views/main I have versions.xml.erb.
I have written an RSpec controller test:
require 'rails_helper'
RSpec.describe MainController, type: :controller do
...
describe '#versions' do
it 'renders versions template' do
get :versions, formats: [:xml]
expect(response).to render_template(:versions)
end
end
end
However, I continue to get this error:
Failure/Error: get :versions, formats: [:xml]
ActionController::UrlGenerationError:
No route matches {:action=>"versions", :controller=>"main", :formats=>["xml"]}
However, when I run rake routes, I get:
versions GET /versions(.:format) main#versions {:formats=>[:xml]}
I still don't understand why I'm getting this error when the route exists with the right parameters.
Ruby 2.1.5
Rails 4.1.6
RSpec 3.8.x
NOTE: I'm constrained to not rewrite any code and can only suggest changes.
Rails' routing uses a format key, not formats. It's automatically supported for all endpoints by default, and will pass on whatever format is required to your controller actions.
I'd suggest the following modifications:
# routes.rb
get 'versions', to: 'main#versions'
# main_controller.rb
def versions
respond_to do |format|
format.xml
end
end
# main_controller_spec.rb
it 'renders the version template' do
get :versions, format: :xml
...
end

UrlGenerationError in rspec test

I've been tasked with creating custom error pages that fall in line with our application. I found https://wearestac.com/blog/dynamic-error-pages-in-rails which works perfectly fine. I don't get any routing errors and manually testing shows that each page renders correctly.
However, when creating controller specs for the controller used to delegate the routes to the views, I am running into an issue. My views are named 404.html.erb, 500.html.erb, 422.html.erb. They are located in app/views. My code is almost exactly the same as what is listed in the link above, however for posterity and clarity, the relevant code is shown below as well as the error message is shown below:
Error message: ActionController::UrlGenerationError: No route matches {:action=>"show", :controller=>"errors", :params=>{:code=>"404"}}
app/controllers/errors_controller.rb:
# frozen_string_literal: true
class ErrorsController < ApplicationController
def show
render status_code.to_s, status: status_code
end
protected
# get status code from params, default 500 in cases where no error code
def status_code
params[:code] || 500
end
end
spec/controllers/errors_controller_spec.rb:
require 'rails_helper'
describe ErrorsController, type: :controller do
describe '#show' do
it 'renders the 404 error page when it receives a 404 status code' do
get :show, params: { code: '404' }
# ive also tried changing the param passed to redirect_to to error_404 to no effect
expect(response).to redirect_to(error_404_path)
end
it 'renders the 422 error page when it receives a 422 status code' do
get :show, params: { code: '422' }
expect(response).to redirect_to(error_422_path)
end
it 'renders the 500 error page when it receives a 500 status code' do
get :show, params: { code: '500' }
expect(response).to redirect_to(error_500_path)
end
end
end
config/routes.rb (only the relevant route, our full routes file is gigantic)
%w(404 422 500).each do |code|
get code, to: "errors#show", code: code, as: 'error_' + code
end
config/application.rb (only the relevant line, everything else is standard):
config.exceptions_app = self.routes
I have tried expanding the routes so that each was explicitly defined, as well as reverting to the first non-DRY form in the link. I've also tried changing the redirect_to call to a render_template call, to no effect.
I've scratched my head for days hoping to figure it out but I've had no luck. Again, the route works perfectly fine in development, but when I try to test that these routes work in rspec, it can't find any route.
The error message is the same for each spec in the file, aside from the status code. Any help would be appreciated!
Try to add :id param to get method. Show action route is expecting an :id param (url for show action without :id param is actually index action url).
get :show, params: { id: 1, code: '422' }

UnknownFormat in rails spec

I'm fairly new to rails and I'm trying to return a javascript file from a controller action. In TDD fashion I'm trying to write a failing test first and although I've fixed a bunch of reasons it's failed since starting, I'm now up against an error that I'm not sure how to fix. If I run it in a browser, it works. But running the spec I get an error "ActionController::UnknownFormat" pointing to the respond_to do |format| line.
Here's the controller action...
def job_board
respond_to do |format|
format.js
end
end
And my spec
require 'rails_helper'
RSpec.describe ApiController, :type => :controller do
describe "when using an invalid url/api key" do
it "should return error" do
get :job_board
expect(response).to have_http_status(400) #bad request
end
end
end
And finally my routes.
Rails.application.routes.draw do
get 'api/job_board' => 'api#job_board', defaults: { format: 'js'}
end
Any idea why the spec is throwing this error? Thanks.
Got it figured out. I had to change the
get :job_board
in the spec to
get :job_board, format: :js

devise_inviteable - issue with GET users/invitation/accept?invitation_token=xxxx re-directed to sign_up

ruby '2.1.2'
rails (4.1.4)
devise (3.2.4)
devise_invitable (1.3.6)
routes.rb
devise_for :users, :controllers => { invitations: 'users/invitations' }
users/invitations_controller.rb
class Users::InvitationsController < Devise::InvitationsController
prepend_before_filter :require_no_authentication, :only => [:edit, :update, :destroy]
# i dont need to override anything
end
rake routes
accept_user_invitation GET /users/invitation/accept(.:format) users/invitations#edit
remove_user_invitation GET /users/invitation/remove(.:format) users/invitations#destroy
user_invitation POST /users/invitation(.:format) users/invitations#create
new_user_invitation GET /users/invitation/new(.:format) users/invitations#new
PATCH /users/invitation(.:format) users/invitations#update
PUT /users/invitation(.:format) users/invitations#update
every part of the application needs to be authenticated - except the accept_user_invitation path
my application controller does have:
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
Not sure why this is not working - anyone care to help me understand what I am doing incorrectly ? Every time I go to the URL I am redirected to signup
Thanks in advance. I am almost ready to roll my own !
I figured out why devise_invitable was re-directing and that was because the invitation_token was incorrect.
When overriding the default behaviour to send your own email
user = User.invite!(params) do |u|
u.skip_invitation = true
u.invitation_sent_at = Time.now
u.invited_by_id = 1
u.invited_by_type = 'User'
end
Looking at the code:
https://github.com/scambra/devise_invitable/blob/master/lib/devise_invitable/model.rb#L200
There is a temporary raw_invitation_token which is the actual token needed for your url and invitation_token which is encrypted. I have to admit there was some confusion here!
In your url generation the token you must use is raw_invitation_token as devise_invitable will decrypt this token: https://github.com/scambra/devise_invitable/blob/master/lib/devise_invitable/model.rb#L277
When I send the email or generate the link this works:
# use the raw_invitation_token rather than invition_token
# when generating your links for the mailer
token = user.raw_invitation_token
puts "#{accept_user_invitation_url(::ActionMailer::Base.default_url_options.merge({:invitation_token => token})) }"
Use the user.raw_invitation_token for your urls and the process works as intended
I have requested that the documents be improved and added a pull request - hope this helps someone

Strange Behaviour with .errors on Factory Girl Stubbed Object

I am using factory_girl_rails (4.2.1) and rspec-rails (2.14.0) to test a simple controller on Rails 4. When testing an error case, I use FactoryGirl.build to build an invalid User object. However, the resulting object does not contain any error in #user.errors; yet expect(assigns(:user)).to have(1).errors_on(:email) in the test case still passes. Why doesn't the FactoryGirl generated object has any error, and how does rspec see the error?
Here are the details and code.
The controller simply creates a User object, then redirect to a verification page if creation was successful or render the form again if there is any error.
class RegistrationController < ApplicationController
def new
end
def create
#user = User.create(params.required(:user).permit(:email, :password, :password_confirmation))
if #user.errors.empty?
redirect_to verify_registration_path
else
render :new
end
end
end
In my error case test, I create a User without 'email' using FactoryGirl. It is expected to create an error entry in #user.errors for the 'email' field AND renders the :new template.
describe RegistrationController do
#... Some other examples ...
describe 'GET create' do
def post_create(user_params)
allow(User).to receive(:create).with(ActionController::Parameters.new({user: user_params})[:user]).and_return(FactoryGirl.build(:user, user_params))
post :create, user: user_params
end
context 'without email' do
before { post_create email: '', password: 'testing', password_confirmation: 'testing' }
subject { assigns(:user) }
it 'build the User with error' do
expect(subject).to have(1).errors_on(:email)
end
it 'renders the registration form' do
expect(response).to render_template('new')
end
end
end
end
However, when I ran the test case, only the 'renders the registration form' example failed, but not the other one.
Failures:
1) RegistrationController GET create without email renders the registration form
Failure/Error: expect(response).to render_template('new')
expecting <"new"> but rendering with <[]>
# ./spec/controllers/registration_controller_spec.rb:51:in `block (4 levels) in <top (required)>'
Finished in 0.25726 seconds
6 examples, 1 failure
Failed examples:
rspec ./spec/controllers/registration_controller_spec.rb:50 # RegistrationController GET create without email renders the registration form
What is strange here is that rspec seems to be able to see an error in #user (hence the first test case passes) but for some unknown reason #user.error.empty? returns true in controller causing it to redirect instead of rendering the :new template (hence the failed second test case). I also confirmed in debugger that #user.error was indeed empty.
Is it something wrong with how FactoryGirl handles error, or am I using it wrong?
Thanks
Two things I want to mention here are:
1. Probably You want to use "Post create" instead of "Get create".
2. Whether email is missing or not is the model's concern, not controller's.
I suggest you use stub to return false for the case that email is missing.
The easiest way is:
User.any_instance.stub(:create).and_return(false)
And maybe you want to change some other things in the controller, like "if #user.errors.empty?"
EDIT: Sorry, "create" actually doesn't return false.
So in your controller
#user = User.new(.....)
if #user.save
...
else
render :new
And in your test use
User.any_instance.stub(:save).and_return(false)