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' }
Related
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.
I want to redirect to 404 if request url contains invalid parameters, but I did not know what is the best way to achieve that.
For example:
The request url: http://example.com/a_path/?invalid_parameter=foo
In this case, I want rails return 404. My stupid way is iterating params and check if contains invalided keys:
def redirect_404_if_invalid_params
valid_params = [:valid_key_1, :valid_key_2, :valid_key_3]
params.each do |key, value|
if !valid_params.include?(key)
redirect 404
end
end
end
But is there any better way to do this trick?
Thanks.
You can use Strong Parameters to filter out the parameters you don't want in an action. To do this, you would have to set:
ActionController::Parameters.action_on_unpermitted_parameters = :raise
so Strong Parameters fails whenever you receive parameters that are not explicitly allowed. Then, specify the parameters you do want to be available, like:
def valid_params
params.require(:param_a).permit(:attr_a, :attr_b)
end
And consume your parameters like valid_params[:attr_a], instead of referencing params directly.
You can catch ActionController::UnpermittedParameters and throw your 404 accordingly or do anything else you may need, though I don't suggest you to this. 404 is the HTTP error for "Not Found", having invalid parameters sound more like 422, "Unprocessable Entity".
Assuming that you want to do exactly that for some reason, here's a compact way.
valid_params = [:valid_key_1, :valid_key_2, :valid_key_3]
if (params.keys - valid_params.map(&:to_s)).present?
not_found
end
Take all params, remove all known valid params. If there's something left, it must be invalid keys. In which case, raise.
BTW, not_found in code above is defined as shown in this answer.
Although, for most cases this is not needed and strong parameters should be used instead. With strong parameters it's another approach: we don't care about unknown parameters, we extract only known good parameters and discard the rest.
I had this same problem and researched extensively. This was my solution:
In my application_controller.rb I did this:
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Exception, with: :not_found
rescue_from ActionController::RoutingError, with: :not_found
def raise_not_found
raise ActionController::RoutingError.new("No route matches # {params[:unmatched_route]}")
end
def not_found
respond_to do |format|
format.html { render :file => "#{Rails.root}/public/404", :layout => false, :status => :not_found }
format.xml { head :not_found }
format.any { head :not_found }
end
end
And then in your routes.rb file do this:
get '*unmatched_route', to: 'application#raise_not_found'
What does the code do.
You probably have an idea what the rescue_from code line of code does.
raise_not_found if for the line of code you put in the routes file. This method triggers the RoutingError and when this happens, not_found is called.
Any url that does not exit in your routes file would yield the 404.html page.
I have a dashboard containing 2 partials. One partial works fine and does what its suppose to (bill). The other partial is setup similar to bill but wont work(request). When I look at my log it shows that the tenant(user) is being queried, also, the 1st partial is queried but the 2nd partial doesn't query. when I went to my dashboard controller and changed the instance of the partial to (Request.new) it works but I can't seem to get it to work right thru the controller. I don't want to have the method in the model. I am using mongoid.
SO here is my render in the dashboard...
<%= render partial: "request", locals: {request: #request} %>
In the partial I have...
<%= form_for [:tenants, request] do |f| %>
And on the dashboard controller I have...
def show
#dashboard = current_tenant
#bill = current_tenant.bill || current_tenant.build_bill
#request = current_tenant.request || current_tenant.build_request
end
(if I change #request = Request.new it works fine but I know that's not right)
The bill partial works and the dashboard finds the tenant but I keep getting
"undefined method `request' for #
Any idea of what I am missing? I compared the bill controller to the request controller and I cant find any differences. When I put the Model.new into the dashboard controller it works but I know this isn't right, its as if the app wont recognize the Request controller...
The error is saying it doesn't recognize "request" method.
Also here is my controller for request...
class Tenants::RequestsController < ApplicationController
before_filter :authenticate_tenant!
def index
#requests = Request.all
end
def show
#request = Request.find(params[:id])
end
def create
if #request = current_tenant.create_request(authorization_params)
redirect_to tenants_dashboard_path, :notice => "#{request.manager_name} has been Authorized!"
else
redirect_to tenants_dashboard_path, :error => "#{request.manager_name} has NOT been Authorized, please try again."
end
end
def edit
end
def update
if current_tenant.request.update_attributes(authorization_params)
redirect_to tenants_dashboard_path, :notice => "You have approved #{request.manager_name} to review your report"
else
redirect_to tenants_dashboard_path, :notice => "#{request.manager_name} is NOT allowed to review your report"
end
end
def destroy
#request = Request.find(params[:request_id])
name = #request.name
if #request.destroy
flash[:notice] = "\"#{name}\" was successfully removed from your profile."
redirect_to #dashboard
else
flash[:error] = "There was an error deleting this managers access."
render :show
end
end
Well it looks like
current_tenant.request has an issue. That means that the method is not available. Assuming you're not trying to access the http request , then you have an issue with the request method.
So your issue is with how you defined the request method (maybe in your model). e.g. is it a class method or a instance method etc.
Without knowing your goal, that's the general answer I can give you. Creating a Request.new could be right depending on your goal, but if your goal is to call the request method, you must make it available to current_tenant
One controller shouldn't be calling your other controller as you have suggested...
I've spent several hours trying to understand why my Rails 4 app doesn't seem to want to route to the expected controller action.
In summary: every single action that I attempt in the browser URL seems to go to the index view, even though my routes appear to be correct. I've attempted to restart the server, etc hoping that might fix it, but right now I'm completely lost.
For example, if I try to access the URL at localhost:3000/leads#new I get the following error message:
Missing template leads/index, application/index with {:locale=>[:en], :formats=>[:html], :handlers=>[:erb, :builder, :raw, :ruby, :jbuilder, :coffee]}. Searched in: * "/Users/me/Sites/azimuth/app/views"
If I add a template for index.html.erb to the app/views/leads folder, then I don't get the error message - however every single path goes to that same index view - leads#show, leads#edit, etc - all of them.
Here is the routes.rb:
Azimuth::Application.routes.draw do
# get 'leads', to: 'leads#new'
resources :applicants
resources :contacts
resources :leads
PagesController.action_methods.each do |action|
get "/#{action}", to: "pages##{action}", as: "#{action}_page"
end
root "pages#home"
end
note that the commented line - get 'leads', to: 'leads#new' - seems to be the only way to properly get the routing to work. Using resources :leads (which I understand is best practice?) is giving me fits.
Here's the leads_controller.rb:
class LeadsController < ApplicationController
def new
#lead = Lead.new
end
def create
#lead = Lead.new(lead_params)
if #lead.save
flash[:success] = "Thank you for reaching out! We'll be in touch soon."
redirect_to 'home'
else
render 'new'
end
end
def index
#lead = Lead.all
end
private
def lead_params
params.require(:lead).permit(:first_name, :last_name, :subject, :message)
end
end
Rake routes - appears that things should work fine. (Note this is just showing the routes relevant to the Leads object).
Prefix Verb URI Pattern Controller#Action
leads GET /leads(.:format) leads#index
POST /leads(.:format) leads#create
new_lead GET /leads/new(.:format) leads#new
edit_lead GET /leads/:id/edit(.:format) leads#edit
lead GET /leads/:id(.:format) leads#show
PATCH /leads/:id(.:format) leads#update
PUT /leads/:id(.:format) leads#update
DELETE /leads/:id(.:format) leads#destroy
I'm very confused, can't seem to track down what's going on, and would appreciate any help!
Correct me if you are wrong, but I think you are trying to access the wrong URL. You said you were visiting localhost:3000/leads#new in your browser. The correct URL for that route would be localhost:3000/leads/new
When are you are defining routes in your config/routes.rb file, the #'s are used to let rails know that you are specifying that a method of one of your controllers should respond to this URL. The actual URL's do not contain #'s (typically speaking).
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)