In my edit action, I have
#item = current_user.shop.items.find(params[:id])
So that the user can only edit items that belong to their shop. If they try to edit an item that does not belong to their shop, then they get an ActiveRecord::RecordNotFound error.
What is the best way of handling this error in situations like this? should i raise an exception? should i redirect somewhere and set the flash (if so, how do i do that), should i just leave it as is? Any advice is appreciated.
Thanks
Add something like the following to your controller:
rescue_from ActiveRecord::RecordNotFound do
flash[:notice] = 'The object you tried to access does not exist'
render :not_found # or e.g. redirect_to :action => :index
end
+1 for the solution of #Koraktor. If you want to avoid ActiveRecord::RecordNotFound, you can use find_by method instead of find.
#item = current_user.shop.items.find_by_id(params[:id])
if #item.nil?
flash[:error] = "Item not found"
else
# Process the #item...
end
Additionally you should set the http status while rendering the template :
rescue_from ActiveRecord::RecordNotFound do
render :not_found, :status => :not_found
end
Check the Rails guide (section 2.2.13.4) for the status list.
As stated somewhere else (for example here ) you should never make a redirection when sending a 40x status, only 30x statuses should allow redirecting. So you may encounter a weird "You are being redirected" page whenever trying to make a redirect with a :not_found status.
Related
def show
begin
#cart = Cart.find(params[:id])
authorize(#cart)
#cart_entries = CartEntry.where(:cart_id => #cart.id)
#products = {}
#pr_references = {}
#cart_entries.each do |cart_entry|
#pr_references[cart_entry.id] = Reference.find(cart_entry.reference_id)
#products[cart_entry.id] = Product.find(#pr_references[cart_entry.id].product_id)
end
rescue ActiveRecord::RecordNotFound => e
respond_to do |format|
format.json {render json: {'error': e}, status: :not_found}
end
end
I want to test when Cart.find() doesn't find the cart and I want to test the method return a 404 HTTP code with the test below.
it 'don\'t find cart, should return 404 error status' do
delete :destroy, params: {id: 123, format: 'json'}
expect(response).to have_http_status(404)
end
Have you got some indications or solution to do that ?
I'm a nooby with ruby on rails, if you have some tips with the code I posted I'll take it.
Thank you :)
It seems some other code is raising an exception before your Cart.find statement is executed. For this reason, the ActiveRecord::RecordNotFound exception is never risen, and it is never captured by the rescue block.
Based on the exception that is raising, it seems you are using Pundit gem for dealing with authorization. The authorization rules offered by this gem are surely running before your show method starts. Probably this is happening as a consequence of a before_filter statement, either in this controller or in a parent controller.
You will need to handle this kind of errors in your application. It may be handy to use a rescue_form statement in a base controller that is inherited by all other controllers, so that you don't have to deal with this kind of errors in every controller.
I'm using CanCanCan in my rails app for authorization. The routes and redirects work fine but I don't manage to display the AccessDenied error message (it worked before but I must have messed something up on the way and now it just won't show).
Here's my code:
controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, error: exception.message
end
...
end
(I changed the default :alert to :error as otherwise I was ending up with an alert from devise ('You are already logged in').
views/index.html.haml (root)
= flash[:error] if flash[:error]
...
Any ideas on how to get it to work again? Thanks in advance.
---EDIT---
Some more fixes I tried (without success):
Replacing error with message and notice
Removing the rescue_from CanCan::AccessDenied method completely - it brought me to the standard CanCan error page (so I know the method itself is working).
Replacing the exception.message with a regular string.
Adding a puts statement before redirection - it gets printed in the console as one would expect.
As #Anand Shah suggest in an answer to this question, I added the following in my view:
- if flash[:error].blank?
%p flash hash is blank
The hash was indeed empty, so it seems like the flash is not being saved at all. Then I added
flash.keep(:error)
in my controller but that didn't change anything.
The reason for this issue turned out to be my routes.rb. In addition to defining the root for devise scope:
devise_scope :user do
root to: "devise/sessions#new"
end
authenticated :user do
root 'application#show', as: :authenticated_root
end
I also had an extra root defined (which basically caused double redirection and therefore loss of the alert):
root to: 'application#show'
The code from the documentation works just fine after the necessary modification (note I could bring it back from error to alert without breaking things):
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, alert: exception.message
end
Have you tried it through a private browser in case the cookies have messed up?
Can you try this format?
redirect_to(root_url, {:flash => { :error => exception.message }})
Have you tried not handling this inline but rather something like:
rescue_from CanCan::AccessDenied do |exception|
flash[:error] = exception.message
redirect_to root_url
end
The reason it was working before is Rails understands :alert and :notice as flash messages during the redirect_to otherwise you should use the "general purpose flash bucket" as they call it via flash: {error: 'your message'}
However the Hash passed to redirect_to can also contain the http status code (:status) and url query parameters (anything that is not :alert, :notice, :status, or :flash and it all just seems like too much noise (IMO) to put a flash message in there to save 1 line of explicit code.
I am developing a new rails app. but found a error:
ActionController::RedirectBackError in UsersController#show
No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].
my code:
rescue_from CanCan::AccessDenied do |exception|
redirect_to :back
end
I have some question for this error:
what is HTTP_REFERER?
why redirect to back will trigger this error?
Anyone has good idea?
what is HTTP_REFERER?
The HTTP referer is an HTTP header field that identifies the address of the webpage (i.e. the URI or IRI) that linked to the resource being requested. This is set by ActionController::Request object.
why redirect to back will trigger this error?
This usually happens when request.env["HTTP_REFERER"] is not set. (I also wonder to know in which case it is set and not set)
You could refer this answer to fix your issue.
(OR) I would highly prefer to define a custom page for access denied and redirect to it instead of redirecting :back (which I think bad idea)
For example from cancan gem docs,
rescue_from CanCan::AccessDenied do |exception|
render :file => "#{Rails.root}/public/403.html", :status => 403, :layout => false
## to avoid deprecation warnings with Rails 3.2.x (and incidentally using Ruby 1.9.3 hash syntax)
## this render call should be:
# render file: "#{Rails.root}/public/403", formats: [:html], status: 403, layout: false
end
Hope this helps!
I'm wondering what is best practive for handling this type of situation in ruby on rails.
users_controller.rb
def show
#user = User.find params[:id]
end
If user is not found it throws an exception which isn't catched anywhere so it will display some ugly things to enduser.
Solution would be pack it into begin...rescue...end block:
def show
begin
#user = User.find params[:id]
rescue
flash[:error] = "User not found"
redirect :action => :index
end
end
although I've never seen such code in any rails article or tutorial I've seen.
What is the proper way of handling this type of situations?
See docs rescue_from
It depends on your requirement.
But you want a general solution then you can have a rescue block in ApplicaionController which will handle the RecordNotFound exception.
You can do
def show
#user = User.find_by_id(params[:id])
unless #user
flash[:error] = "User not found"
redirect :action => :index
end
end
But you cant expect you will call a link with id which is not in the db from within the application. Please see the answer of the question
The development environment will show you ugly error messages, but a production environment will just give an HTTP 404 error (page not found) when the id is invalid.
I think that you may be able to fix this with
#user = User.find(params[:id] = current_user)
Here I've got two controller methods:
def invite
if request.post?
begin
email = AccountMailer.create_invite(#user,url)
AccountMailer.deliver(email)
flash[:notice] = "Invitation email sent to #{#user.email}"
rescue
#mail delivery failed
flash[:error] = "Failed to deliver invitation"
end
redirect_to :action => :show, :id => #user.id
end
end
and
def show
#title = "User #{#user.full_name}"
end
The problem is, when I send an invitation, and get redirected to ./show, I see no messages at all. If I change redirect_to to render, the message appears. Still, isn't it intended for flash to work in the immediate subsequent requests?
BTW, I'm using Rails+Passenger setup, could it be so that redirected request goes to another application instance?
The rescue block is setting flash[:error], not flash[:notice]. Is your view actually rendering both?
Googled better and found this discussion:
http://www.mail-archive.com/activescaffold#googlegroups.com/msg04284.html
The solution is there: replace the plugin with
script/plugin install git://github.com/ewildgoose/render_component.git -r rails-2.3 --force
Though I don't use ActiveScaffold, there is some legacy code that depends on render_component plugin. Updating plugin to branch version worked, though I'm planning to get rid of it completely.