In my index action, I have the following code:
#hotels = Hotel.where(lang: request.headers['Accept-Language']).includes(:contacts)
raise ActiveRecord::RecordNotFound if #hotels.blank?
I am raising the exception because I want it to be handled by an error handling code (based on rescue_from)
Is there a better way to write the code so that it does the same thing, i.e. raise the exception? I can do first! (notice the bang) when retrieving a single record, but as for collections, it seems like there is no way to do the same thing (no where!, all! ...)
Does it make sense at all?
In your controller you can add before_filter
before_filter :check_hotels, :only => [:index]
def index
end
private
def check_hotels
#hotels = Hotel.where(lang: request.headers['Accept-Language']).includes(:contacts)
redirect_to root_path, :notice => "No hotels present." if #hotels.blank?
end
Ofcourse you can give any path othet than root_path, its just an example I have shown
class HotelsController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :blank_list
private
def blank_list
logger.error "No Hotel Found With #{request.headers['Accept-Language']}"
redirect_to root_path, notice: 'No hotels present'
end
end
Related
I'm writing a show action in a controller and want to branch if no id received from params[:id] exists in my database.
My app is still being created. I've tried conditional branching and exception handling. However, they didn't work as expected.
class ArticlesController < ApplicationController
#...
def show
begin
#article = Article.find(params[:id])
rescue
render root
end
end
#...
end
I expect to be redirect to the root if any error occurs.
The above code returns the record if it is found successfully. However, an ActiveRecord::RecordNotFound in ArticlesController#show occurs if no record is found.
How should I write code for this error?
How should I write code for this error?
The short answer is "you shouldn't".
Exceptions should, IMO, be exceptional. It sounds like you might expect this circumstance, so I suggest you write code that handles the scenario without raising an exception.
If you really think you'll have circumstances where the Article record does not exist for the given params[:id] (seems a little odd, but I guess it's possible), then I would suggest that you use .find_by instead of .find:
class ArticlesController < ApplicationController
#...
def show
if #article = Article.find_by(id: params[:id])
# do something with the article
else
render root
end
end
#...
end
It seems like to you might want to do a redirect instead of a render:
class ArticlesController < ApplicationController
#...
def show
if #article = Article.find_by(id: params[:id])
# do something with the article
else
redirect_to root_path
end
end
#...
end
But, maybe not...
Also, rescuing everything like this:
def show
begin
#article = Article.find(params[:id])
rescue
render root
end
end
...is generally viewed as code smell. There are a lot of articles on the interwebs about why.
You can handle the exception from ArticlesController but I advise put the code at
ApplicationController like this:
rescue_from ActiveRecord::RecordNotFound do
redirect_back fallback_location: root_path, alert: t("resource_not_found")
end
Maybe You can code like this:
def show
#article = Article.find_by(id: params[:id])
unless #article.present?
flash[:error] = "Not Found."
redirect_to root_path
end
end
why write so much code, while you can achieve with two lines only.
class ArticlesController < ApplicationController
def show
#article = Article.find_by(id: params[:id])
redirect_to root_path unless #article.present?
end
end
Your code should be written like this:
def show
begin
#article = Article.find(params[:id])
rescue
redirect_to root_path
end
end
What is the best way to handle the error then ID is not found?
I have this code in my controller:
def show
#match = Match.find(params[:id])
end
I was thinking about something like this:
def show
if #match = Match.find(params[:id])
else
render 'error'
end
end
But I still get:
ActiveRecord::RecordNotFound in MatchesController#show
Couldn't findMatch with 'id'=2
Why?
What is the correct solution?
Rescue it in the base controller and leave your action code as simple as possible.
You don't want to deal not found exception in every action, do you?
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :render_404
def render_404
render :template => "errors/error_404", :status => 404
end
end
By default the find method raises an ActiveRecord::RecordNotFound exception. The correct way of handling a not found record is:
def show
#match = Match.find(params[:id])
rescue ActiveRecord::RecordNotFound => e
render 'error'
end
However, if you prefer an if/else approach, you can use the find_by_id method that will return nil:
def show
#match = Match.find_by_id(params[:id])
if #match.nil? # or unless #match
render 'error'
end
end
You can use find_by_id method it returns nil instead of throwing exception
Model.find_by_id
There is two approaches missing:
One is to use a Null-Object (there I leave research up to you)
Te other one was mentioned, but can be placed more reusable and in a way more elegantly (but it is a bit hidden from you action code because it
works on a somewhat higher level and hides stuff):
class MyScope::MatchController < ApplicationController
before_action :set_match, only: [:show]
def show
# will only render if params[:id] is there and resolves
# to a match that will then be available in #match.
end
private
def set_match
#match = Match.find_by(id: params[:id])
if !#match.present?
# Handle somehow, i.e. with a redirect
redirect_to :back, alert: t('.match_not_found')
end
end
end
I have defined my own method authorize_user in one of my controllers, as:
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
I want to rescue from the CanCan exception (or any other exception for that matter). I have used Rolify in my app. How do I rescue and redirect to the root_url of my app with a custom message?
I have tried the following options, but none of them worked:
Try 1:
rescue CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end
Error in this case: syntax error, unexpected keyword_do, expecting '('
Try 2:
rescue CanCan::AccessDenied
redirect_to root_url, :alert => "Unauthorized Access"
Error in this case: Render and/or redirect were called multiple times in this action
How do I solve this issue?
This is my controller code:
class CabsController < ApplicationController
before_action :set_cab, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
after_action :authorize_user
# Some basic CRUD actions
private
def set_cab
#cab = Cab.find(params[:id])
#operator = Operator.find(params[:operator_id])
end
def cab_params
params.require(:cab).permit(:category, :number)
end
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
end
I think you could try the rescue_from method.
For example, your ApplicationController, would look like this:
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied, with: :not_authorized
#other stuff
private
def not_authorized
redirect_to root_url, alert: "Unauthorized Access"
end
end
Since the question was updated with more code, here is additional information:
Some suggestions:
Make the :authorize_user a before_action as well. That way you don't need to worry about code running in the action even when the user was not allowed to do stuff.
You might also need to add the same :only option as for the :set_cab since you use the #operator instance variable.
Last, a personal code style preference is that I would have changed the if ! to unless to increase reading flow.
Try redirect_to(...) and return.
Agreeing with Jakob W I would like to point, that authorization (and authentication) MUST be performed only before action. What is the purpose of any authorization and exception raising when DB transaction, reading/writing to filesystem etc have been already done?
And using before_action has no problem with Render and/or redirect were called multiple times in this action - there will be only one redirect - in exception handling before controller method call.
So, I recommend next code (updated Jakob W's sample):
class CabsController < ApplicationController
#...
before_action :authorize_user
private
#...
def authorize_user
if !((current_user.has_role? :admin, #operator) || (current_user.has_role? :super_admin))
raise CanCan::AccessDenied
end
end
end
class ApplicationController < ActionController::Base
rescue_from CanCan::AccessDenied, with: :not_authorized
#other stuff
private
def not_authorized
redirect_to(request.referrer || root_path), alert: "Unauthorized Access"
end
end
Could I recommend another authorization gem? I think this one is flexible and easy to use - pundit (https://github.com/elabs/pundit). Page on github has some useful tips on authorization.
I have stumbled upon a situation where my application looks for an id that does not exist in the database. An exception is thrown. Of course, this is a pretty standard situation for any web developer.
Thanks to this answer I know that using rescue deals with the situation pretty neatly, like so:
def show
#customer = Customer.find(params[:id])
rescue ActiveRecord::RecordNotFound #customer with that id cannot be found
redirect_to action: :index #redirect to index page takes place instead of crashing
end
In case the customer cannot be found, the user gets redirected to the index page. This works absolutely fine.
Now, this is all nice, but I need to do the same rescue attempts in actions like show, edit, destroy, etc, i.e. every controller method that needs a specific id.
Having said that, here's my question:
Isn't there any way to generally tell my controller that if it can't find the id in any of its methods, it shall redirect to the index page (or, generally, perform a specific task)?
You must use rescue_from for this task. See example in the Action Controller Overview Guide
class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
redirect_to action: :index
end
end
Rails has a built-in rescue_from class method:
class CustomersController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :index
...
end
If you're talking about doing this within a single controller (as opposed to doing this globally in every controller) then here are a couple options:
You can use a before_filter to setup your resource:
class CustomerController < ApplicationController
before_filter :get_customer, :only => [ :show, :update, :delete ]
def show
end
private
def get_customer
#customer = ActiveRecord.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to :action => :index
end
end
Or you might use a method instead. I've been moving in this direction rather than using instance variables inside views, and it would also help you solve your problem:
class CustomerController < ApplicationController
def show
# Uses customer instead of #customer
end
private
def customer
#customer ||= Customer.find(params[:id])
rescue ActiveRecord::RecordNotFound
redirect_to :action => :index
end
helper_method :customer
end
In certain cases, I would recommend that you use Model.find_by_id(id) as opposed to Model.find(id). Instead of throwing an exception, .find_by_id returns nil. if the record could not be found.
Just make sure to check for nils to avoid NoMethodError!
P.S. For what it's worth, Model.find_by_id(id) is functionally equivalent to Model.where(id: id), which would allow you to build out some additional relations if you want.
I handle RecordNotFound error in my application_controller.rb as follow:
rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found
private
def record_not_found
flash[:error] = "Oops, we cannot find this record"
redirect_to :back
end
But I would like to get more information, such as class/table name of which record was not found.
How should I go about it?
Thank you.
I had some success with this:
# in app/controllers/application_controller.rb
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
def record_not_found exception
result = exception.message.match /Couldn't find ([\w]+) with 'id'=([\d]+)/
# result[1] gives the name of the model
# result[2] gives the primary key ID of the object that was not found
end
HTH
EDIT: Whitespace error removed at the end of the Regex. Thanks to the commenters. :)
You can define a parameter in your rescue handler and exception will be passed there.
def record_not_found exception
flash[:error] = "Oops, we cannot find this record"
# extract info from exception
redirect_to :back
end
If you can't get that info from the exception, you're out of luck (I think).
Say for example,
begin
#user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:notice] = "#No such record in User for id :: {params[:id]} on #{action_name}"
end
UPDATE
flash[:notice] = t('flash.recordnotfound',:class_name => self.class.name, :column_name => params[:id], :action_name => action_name)
Now in your config/locales/en.yml (this would help translate, refer to i18n here)
flash:
recordnotfound: "Sorry, no record od %{column_name} in class %{class_name} was found on you action %{action_name}"
If you do not want to use locales just put up this information in flash[:notice] itself.
More dynamic ?
Write a function and use the same flash [:notice] there. Wont hurt at all.
want more data ?
Heres a quick solution, i always <%= params%> in my views to know easily whats going and whats coming. You can then open your rails console and play along with different actions and so on.
user = User.new
user.save
user.errors.messages
All of this is good enough data, i think.
Good luck.
Once you instantiate the model, you can check something like.
human = Human.new
human.errors
Check this in rails console so you could play with it and use it in the main controller.
rescue_from ActiveRecord::RecordNotFound do |exception|
raise ActiveRecord, exception.message, exception.backtrace
end
EDIT
Make sure you the application controller extends the base.
class ApplicationController < ActionController::Base
rescue_from Exception, :with => :record_not_found
private
def record_not_found(e)
flash[:error] = "Oops, we cannot find this record" + e.message
redirect_to :back
end
end