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
Related
Suppose we have a rails API. In many controllers methods I need to set my current_object thanks to params from the request. I can then set a before_action like:
def set_current_object
if My_object.exists? params[:id]
#current_object = My_object.find params[:id]
else
render json: {error: 'Object not found'}.to_json, status:404
end
end
This is ok. But I would like to set current_object dynamically in my controllers methods. Imagine I have a show method in one controller where I need to use my current_object like:
def show
render json: {object_name: current_object.name}.to_json, status: 200
end
current_object would be a helper method like:
def current_object
if My_object.exists? params[:id]
return My_object.find params[:id]
else
render json: {error: 'Object not found'}.to_json, status:404
end
end
Then, if My_object.exists? params[:id] is false I would like to send a 404 and to stop my controller method. Like written here, it is obviously not working. Any suggestion?
You're on the right track. Typically you would implement this sort of "lazy-loading" as a method which memoizes its return value using the ||= idiom.
You simply need to modify your current_object helper so that it can trigger a 404 error when it's unable to return a valid value. Typically you would do this by raising a recognizable exception such as an ActiveRecord::RecordNotFound, and handling this in your controller with a rescue_from clause.
class ApplicationController
def current_object
if My_object.exists? params[:id]
# memozie the value so subsequent calls don't hit the database
#current_object ||= My_object.find params[:id]
else
raise ActiveRecord::RecordNotFound
end
end
rescue_from ActiveRecord::RecordNotFound with: :show_404
def show_404
render json: {error: 'Object not found'}.to_json, status:404
end
end
Now, because you're following a pretty standard Rails convention of handling ActiveRecord::RecordNotFound at the top-level of your controller hierarchy, you can now clean up your current_object method considerably. Instead of checking for the presence of a record, just try to find the record by id. If it doesn't exist, ActiveRecord will automatically raise the exception for you. In fact, your entire current_object method should be a single line of code:
class ApplicationController
def current_object
#current_object ||= My_object.find(params[:id])
end
rescue_from ActiveRecord::RecordNotFound with: :show_404
def show_404
render json: {error: 'Object not found'}.to_json, status:404
end
end
Assuming My_object is a model, if you simply use find, then a params[:id] that doesn't exist in the database will raise an ActiveRecord::RecordNotFound error, and Rails' ActionController::Base will catch the exception and render a 404 by default:
def current_object
My_object.find params[:id]
end
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
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
I've got a little issue with rails, I want to be able to do something like this to avoid multiple redirections :
def render_not_found
not_found
end
private
def not_found
redirect_to website_url(#website), :status => 301 and return return
end
return return doesn't work of course!
Using: rails 3.2.0
There's a few ways to do this. One way is to define and raise a custom error, and have a handler that redirects when that happens.
application_controller.rb
Class ApplicationController < ActionController::Base
around_filter :catch_errors
def catch_errors
yield
rescue SiteNotFoundError
redirect_to website_url(#website), :status => 301
rescue ActiveRecord::RecordNotFound
render 404
rescue ...
...
...
end
end
class SiteNotFoundError < StandardError; end
in your controller
def your_action
raise SiteNotFoundError if (some condition)
end
or in a before filter
before_filter :ensure_valid_site
def ensure_valid_site
raise SiteNotFoundError if ....
end
I usually put my redirections for errors in before_filters.
But if you really want to do this, you can do it like this... but I'm warning you
it ain't pretty.
def render_not_found
not_found(binding)
end
private
def not_found(b)
redirect_to website_url(#website), :status => 301
b.eval('return')
end
It's very simple, I want to handle a normal [show] request with a call to DataMapper like I did in Merb.
With ActiveRecord I could have done this:
class PostsController
def show
#post = Post.get(params[:id])
#comments = #post.comments unless #post.nil?
end
end
and it handles the 404 by catching the resource's exceptions.
DataMapper instead doesn't do this automatically so right now I'm solving it with this solution:
[moved in the answers]
It is possible to tell the controller to halt inside the not_found function?
I like to use exception throwing, and then use ActionController's rescue_from.
Example:
class ApplicationController < ActionController::Base
rescue_from DataMapper::ObjectNotFoundError, :with => :not_found
def not_found
render file => "public/404.html", status => 404, layout => false
end
end
class PostsController
def show
#post = Post.get!(params[:id]) # This will throw an DataMapper::ObjectNotFoundError if it can't be found
#comments = #post.comments
end
end
Done 'the old Merb way':
class ApplicationController
def not_found
render file: "public/404.html", status: 404, layout: false
end
end
class PostsController
def show
#post = Post.get(params[:id])
not_found; return false if #post.nil?
#comments = #post.comments
end
end
again: It is possible to tell the controller to halt inside the not_found function instead of explicitly calling 'return false' in the show action?
edit: thanx to Francois that found a better solution:
class PostsController
def show
#post = Post.get(params[:id])
return not_found if #post.nil?
#comments = #post.comments
end
end
As DM documentation says, you can use #get!