Custom error page in Rails for specific actions - ruby-on-rails

I have a controller MyController that some of his actions cannot return full HTML pages but only the content without HTML/HEAD/BODY ...tags.
The default error pages are 500.html etc. are indeed full HTML pages and for these MyController:actions I need to somehow get the error pages in a non-full page format.
One way of doing it is:
Override the "render_optional_error_file()" inside the MyController and redirect in case the action is one my special actions to a different version of the error pages (content only, non-full page tags).
Will this work? any other way out there?
BTW: I am working with rails 2.3.9.
Thanks,
Erez

It's a bit of a difficult problem, particularly in the case of the 500 error. Since the 500 will render when the application encounters and error, you can't/shouldn't have a dynamic page that is handled by a controller - since this controller could be the source of the problem.
However, in the case of the 404, you could setup an ErrorsController with a 404 action, and use a different layout, depending on whether or not the request was an XHR request or not.
Then all you need is a method in your ApplicationController like:
def render_404
render "errors/404", :status => 404
end
Which will allow you to call your custom error handler wherever you like.
You could extend this further to add support for the 500 error too (where appropriate) but keep in mind that you will always need the static 500.html in your public folder, incase of a larger problem.

Related

Ruby on Rails Write a redirect for a dynamic URL to a static URL

Is there a simple way to write a ruby if statement to redirect a dynamic page to a static page?
I’m writing this in my pages controller where the site pages are generated. This is what I’m attempting to do.
If “/index/page1”
redirect_to “page2”
return
end
This redirects all the pages created in the pages controller to “page2”. I know the syntax is incorrect. I need help in writing out the correct way to test for the first condition.
Any help is appreciated. Thank you!
Here is an update / more information to my question.
Here is my show action in the PagesController
def show
#page = Page.find_by_url_path("/#{params[:url_path]}")
layout = "templates/#{#page.pageable.class.name.underscore}"
respond_to do |format|
format.html { render layout: layout }
end
I need to write an if statement that looks for one specific URL that gets generated. This page is created by the show action.
e.g. https://host.com/products/page1
Then redirect it to another specific URL. This is a static page on the site.
e.g. https://host.com/page2
I am having difficulty in writing the if statement to find the first page. This is what I've tried.
if "/products/page1"
redirt_to "/page2"
return
end
Depending where I put the code within the show action, I either get a double render error (as subparry explains below). Or I redirect all the pages generated through the show action to "/page2".
This application was written by a more experienced Ruby developer and I'm doing my best to maintain / update it. If I need to post more detailed information, please let me know. Thank you.
Well, as usual with these kind of requirements, there are many ways to achieve it.
As you know, different pages (views) in controllers are represented by instance methods (or Actions), for example I can imagine your PagesController looking something like this:
class PagesController < ApplicationController
def page1
# do something...
end
def page2
#do something else...
end
end
So, the easiest way would be to trigger a redirect from page1 redirect_to "https://host.com/page2", I don't know the reasons behind the decision to redirect. If it is a temporary redirect, it might be the best solution because of ease of change later, but if it is a more permanent redirection, I would implement it at the web server level (Nginx for example)
It depends on your use case.
PS: Don't forget that if you redirect in your action, it does not imply a return, so if you have more code below and another call to render or redirect, it will fail (double render error) so either you remove further renders/redirects or insert an early return.
EDIT:
Ok, now I understand better your case. You have a model called Pages which has a column called url_path which details the location of each page.
So, if I understand correctly, you'll have to do the conditional statement like this:
def show
if params[:url_path] == 'page1'
redirect_to 'https://host.com/page2'
return
end
# Rest of action code...
end
I don't know for sure how are paths stored in url_path, but you get the idea!
PS2: When you write if 'products/page1' you are basically saying if true and always entering the condition because only nil and false are falsy values, everything else is truthy.

Rails: Model.find() or Model.find_by_id() to avoid RecordNotFound

I just realized I had a very hard to find bug on my website. I frequently use Model.find to retrieve data from my database.
A year ago I merged three websites causing a lot of redirections that needed to be handled. To do I created a "catch all"-functionality in my application controller as this:
around_filter :catch_not_found
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound
require 'functions/redirections'
handle_redirection(request.path)
end
in addition I have this at the bottom of my routes.rb:
match '*not_found_path', :to => 'redirections#not_found_catcher', via: :get, as: :redirect_catcher, :constraints => lambda{|req| req.path !~ /\.(png|gif|jpg|txt|js|css)$/ }
Redirection-controller has:
def not_found_catcher
handle_redirection(request.path)
end
I am not sure these things are relevant in this question but I guess it is better to tell.
My actual problem
I frequently use Model.find to retrieve data from my database. Let's say I have a Product-model with a controller like this:
def show
#product = Product.find(params[:id])
#product.country = Country.find(...some id that does not exist...)
end
# View
<%= #product.country.name %>
This is something I use in some 700+ places in my application. What I realized today was that even though the Product model will be found. Calling the Country.find() and NOT find something causes a RecordNotFound, which in turn causes a 404 error.
I have made my app around the expectation that #product.country = nil if it couldn't find that Country in the .find-search. I know now that is not the case - it will create a RecordNotFound. Basically, if I load the Product#show I will get a 404-page where I would expect to get a 500-error (since #product.country = nil and nil.name should not work).
My question
My big question now. Am I doing things wrong in my app, should I always use Model.find_by_id for queries like my Country.find(...some id...)? What is the best practise here?
Or, does the problem lie within my catch all in the Application Controller?
To answer your questions:
should I always use Model.find_by_id
If you want to find by an id, use Country.find(...some id...). If you want to find be something else, use eg. Country.find_by(name: 'Australia'). The find_by_name syntax is no longer favoured in Rails 4.
But that's an aside, and is not your problem.
Or, does the problem lie within my catch all in the Application Controller?
Yeah, that sounds like a recipe for pain to me. I'm not sure what specifically you're doing or what the nature of your redirections is, but based on the vague sense I get of what you're trying to do, here's how I'd approach it:
Your Rails app shouldn't be responsible for redirecting routes from your previous websites / applications. That should be the responsibility of your webserver (eg nginx or apache or whatever).
Essentially you want to make a big fat list of all the URLs you want to redirect FROM, and where you want to redirect them TO, and then format them in the way your webserver expects, and configure your webserver to do the redirects for you. Search for eg "301 redirect nginx" or "301 redirect apache" to find out info on how to set that up.
If you've got a lot of URLs to redirect, you'll likely want to generate the list with code (most of the logic should already be there in your handle_redirection(request.path) method).
Once you've run that code and generated the list, you can throw that code away, your webserver will be handling the redirects form the old sites, and your rails app can happily go on with no knowledge of the previous sites / URLs, and no dangerous catch-all logic in your application controller.
That is a very interesting way to handle exceptions...
In Rails you use rescue_from to handle exceptions on the controller layer:
class ApplicationController < ActionController::Base
rescue_from SomeError, with: :oh_noes
private def oh_noes
render text: 'Oh no.'
end
end
However Rails already handles some exceptions by serving static html pages (among them ActiveRecord::RecordNotFound). Which you can override with dynamic handlers.
However as #joshua.paling already pointed out you should be handling the redirects on the server level instead of in your application.

Rails 3 Specify Fallback Format if MissingTemplate

I have a rails 3.2 application that recently I added mobile-fu gem to, in order to add separate mobile views.
There were a few hiccups but, for the most part, it works wonderfully.
However, I've only made mobile views for a handful of pages. When I attempt to go to a page that does not have a mobile view, from a mobile device, I get:
Missing template after_hour_it_supports/index, application/index with {:locale=>[:en], :formats=>[:mobile], :handlers=>[:erb, :builder, :prawn, :prawn_dsl]}
Which is what I would expect.
However, it is necessary to display some pages which might never have mobile counterparts. Specifically, there is a page to approve something. The thing that they are approving might never have a mobile view made for it, but the approval page already has a mobile view. My current approach, is to use an iframe to display the possibly non-mobile view. Of course, if a mobile view exists, I would prefer to use it over the non-mobile view (still in the iframe).
So what I would like to do is to attempt to render template with the :mobile format, but if the mobile format does not exist, to render with the :html format, which seems like something rails already does based on the :formats array mentioned in the MissingTemplate exception. I can find some documentation on how to set the :formats array when calling render, but I would like to do this automatically, without having to modify every existing response.
How do I modify :formats=>[:mobile] to be :formats=>[:mobile, :html] on an application level?
I finally found an answer which seems to work for me (the answer by Will Madden).
Specifically, I used his suggestion to override the formats= in my ApplicationController. Which he says is the same way that rails already adds this exact same functionality for the :js format.
The specific method he writes in his answer looks like this:
class ApplicationController
...
def formats=(values)
values << :html if values == [:mobile]
super(values)
end
...
end
This is by far the most elegant solution I have found for my circumstances. However, it fails whenever the controller for the page in question contains a respond_to block for the current action.

How to profile a rails controller that returns a json response with rack-mini-profiler?

I am using rack-mini-profiler in my rails 3.2 project.
In gemfile:
gem 'rack-mini-profiler'
Everything works great. But my application is mostly a set of json endpoints. So while it is very useful to be to able to inspect the performance of html pages, I would like to also be able to see the performance of controllers that return json.
Example of my controller:
class UsersController < BaseController
def json_method
# you don't see the mini profiler ui for this controller
render json: { users: [:foo, :bar]}
end
end
If I go to localhost:3000/users/json_method, I see my json response but not the profiler ui.
In development, by default, the rack-mini-profiler gem collects the previous JSON call and presents it in the menu accessible from the HTML page. No code change required.
So, make your JSON request, then hit any other HTML page and it will be available in the list. We use this to great effect. If your Rails app is a JSON API service only, make sure you have a 404.html in your public folder at least, then hit something like:
http://localhost/404.html
In the second tab, you can visit this URL /rack-mini-profiler/requests Where you can see the last request log
if rack mini profiler can’t display the results, it will collect them until it can on the next HTML page. So, the solution that I am using is to:
make the JSON request and
then hit an HTML page of my choice.
The results will appear, along with the most recent HTML profile.
http://tech.eshaiju.in/blog/2016/02/25/profile-api-endpoints-using-rack-mini-profiler/
As a first solution, you can just set the format to html, and render inside the html page:
The controller:
class UsersController < BaseController
def json_method
#users_json { users: [:foo, :bar]}
render 'index', formats: ['html']
end
end
And in app/views/users/index.html.erb:
Users:<br/>
<%= #json.inspect %>
I don't care so much about my json result, now I have the profiling ui.
A solution where I have the profiling ui without changing my controller would be much better.
Note that in a new Rails API project initialized using rails new api_name --api, the ApplicationController inherits from ActionController::API, instead of ActionAcontroller::Base. In this case, mini-profiler might not load when your HTML page is shown.
I had to change the base class to ActionController::Base to make it work. If in your app you see no requests to load resources from mini-profiler on your HTML page, you may want to try this change. Took me a long while to figure out.
Also note that you do need to have at least the <body> tag in your template to be rendered, otherwise the mini-profiler divs will not be properly injected.

Rails 3 Layout + Querystring Problem

I'm trying to setup an ajax-based website that loads a fragment of a webpage when a specific combination of GET variables and HTTP Headers are given.
GET /normal/html/page?ajax=true
X-ajax: true
What I've setup in my controller is:
before_filter do |controller|
if request_by_ajax?
ApplicationController.layout false
end
end
This works, but only in development mode. You see when I attempt to access the given page it only gives the fragmented (layout free) page.
When the normal page is accessed without ajax:
GET /normal/html/page
This returns only the view for that page and NOT the layout around it. But if I access that page when the webserver is reloaded then it returns the layout and when its accessed afterwards via AJAX it returns the layout + the view. Clearly there is a caching issue here.
I would really like to keep the same route for the page. If there is an ajax call then I would prefer to figure out the response based on the querystring and request header values. But rails prefers to classify querystring parameters and request headers as meaningless when serving a file (i.e. /normal/html/page and /normal/html/page?ajax=true) return the same actual template file (this what I assume).
Any idea how to get around this?
I figured out the issue.
It turns out that the layout call must be defined early on in the application controller:
---- application_controller.rb
layout :choose_layout
def choose_layout
if request_by_ajax?
false
else
'application'
end
end
Works like a charm.

Resources