How do I show an error for unauthorized can can access
Asked Answered
S

3

5

I am using Bootstrap, which has div class="alert notice" that has a bunch of classes for various notice messages.

I also have an AJAX destroy action for a comment, that I have added cancan authorization on. When I try to delete a comment that the current_user doesn't have access to it doesn't work - which is correct.

But what I want to happen is for an error message to pop-up, in a Bootstrap style'd div for 5 - 10 seconds and then disappear.

This is the destroy action on my CommentsController.rb

  def destroy
    respond_to do |format|
      if @comment.destroy
          format.html { redirect_to root_url, notice: 'Comment was successfully deleted.'  }
          format.json { head :no_content }
          format.js   { render :layout => false }      
      else
          format.json { render json: @comment.errors, status: :unprocessable_entity }  
      end
    end        
  end

Where I have the @comment set in a private method in the same controller:

  private
    def set_comment
      @comment = current_user.comments.find(params[:id])
    end

This is my comments/destroy.js.erb

$('.delete_comment').bind('ajax:success', function() {  
        $(this).closest('div#new_comment').fadeOut();
});  

But that doesn't affect unauthorized access.

In my ability.rb, I have this:

can :manage, Comment, user_id: user.id

In my log when I try to delete a comment that I don't have access to, I get this in my log:

Started DELETE "/comments/5" for 127.0.0.1 at 2014-10-16 02:56:53 -0500
Processing by CommentsController#destroy as JS
  Parameters: {"id"=>"5"}
  User Load (0.4ms)  SELECT  "users".* FROM "users"  WHERE "users"."id" = 1  ORDER BY "users"."id" ASC LIMIT 1
  FamilyTree Load (0.2ms)  SELECT  "family_trees".* FROM "family_trees"  WHERE "family_trees"."user_id" = $1 LIMIT 1  [["user_id", 1]]
  ReadMark Load (0.1ms)  SELECT  "read_marks".* FROM "read_marks"  WHERE "read_marks"."user_id" = $1 AND "read_marks"."readable_type" = 'PublicActivity::ORM::ActiveRecord::Activity' AND "read_marks"."readable_id" IS NULL  ORDER BY "read_marks"."id" ASC LIMIT 1  [["user_id", 1]]
  Comment Load (0.3ms)  SELECT  "comments".* FROM "comments"  WHERE "comments"."user_id" = $1 AND "comments"."id" = $2 LIMIT 1  [["user_id", 1], ["id", 5]]
Completed 404 Not Found in 8ms

ActiveRecord::RecordNotFound - Couldn't find Comment with 'id'=5 [WHERE "comments"."user_id" = $1]:

Which is perfect.

All I want to do is show an appropriate error in a Bootstrap alert that disappears in a few seconds.

How do I accomplish that?

Shambles answered 16/10, 2014 at 8:7 Comment(0)
F
5

For the first, if you use cancan - just use cancan:

#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  load_and_authorize_resource #this will set @comment by your ability and authorize action
  ...
end

This will raise CanCan::AccessDenied instead of ActiveRecord::RecordNotFound error.

Let's catch it in ApplicationController with rescue_from

#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  ...
  rescue_from CanCan::AccessDenied do |exception|
    @error_message = exception.message
    respond_to do |f|
      f.js{render 'errors/error', status: 401}
    end
  end
end

For popup notifications I use PNotify library http://sciactive.github.io/pnotify/ It will show error in top right conner and then hide. Just include it in your project and you can show the errors like this:

#app/views/errors/error.js.erb
new PNotify({
  title: 'Oh No!',
  text: '<%=@error_message%>',
  type: 'error'
});

This code lets you avoid of catching ActiveRecord::RecordNotFound error as of bad practice.

UPDATE

I forgot something! You have to remove set_comment method and before_action or write it like this:

before_action :set_comment
...
private
def set_comment
  @comment ||= current_user.comments.find(params[:id])
end

This callback overwrote @comment variable from load_and_authorize_resource in your code. Cancan makes this helper unneeded because it loads resource by load_and_authorize_resource

UPDATE2

You also need to make sure that you are using the latest version of cancan with rails4 from CanCanCommunity because original old version doesn't support rails4

Just use this in you Gemfile

gem 'cancancan', '~> 1.9'

instead of

gem 'cancan'
Forgery answered 18/10, 2014 at 18:15 Comment(5)
I like this....but, it doesn't work for me. I still get the ActiveRecord::RecordNotFound error in my logs and nothing happens on the front-end. By the way, I am using the pnotify-rails gem - github.com/navinpeiris/pnotify-railsShambles
Should I do load_and_authorize_resource before the before_action :set_comment or after?Shambles
you may remove before_action :set_comment. load_and_aurthorize_resource do the same job.Forgery
When I do that, the authorization doesn't work at all - i.e. I am allowed to delete things I shouldn't be able to delete. Does that mean that my ability.rb rules are insufficient?Shambles
Let us continue this discussion in chat.Forgery
C
2

In your comments/destroy.js.erb file add the following code:

<% if defined?(@error) %>
  $('.alert.notice').text("<%= @error %>");
  $('.alert.notice').show();
  setTimeout(function() {
    $('.alert.notice').fadeOut('fast');
  }, 5000);
<% end %>

Change the set_comment method to use find_by to avoid getting RecordNotFound exception:

def set_comment
  @comment = current_user.comments.find_by(id:params[:id])
end

then in your controller you can modify the destroy action as follows:

def destroy
respond_to do |format|
  if @comment.present?
      @comment.destroy 
      format.html { redirect_to root_url, notice: 'Comment was successfully deleted.'  }
      format.json { head :no_content }
      format.js   { render :layout => false }      
  else
      format.html { redirect_to root_url, notice: 'unauthorized access error.'  }
      format.js { @error = 'unauthorized access error' }  
  end
end        

end

Cloddish answered 18/10, 2014 at 16:34 Comment(3)
This is nice and elegant, but doesn't work for me. I still get the ActiveRecord error.Shambles
ok please see my updated solution to get around the ActiveRecord errorCloddish
@railsme Actually helped me figure it out. It was a cancan issue. I need to use cancancan.Shambles
L
0

I place this in app/controllers/application_controller.rb

## Handling unauthorized access: https://github.com/CanCanCommunity/cancancan#handle-unauthorized-access
  rescue_from CanCan::AccessDenied do |exception|
    respond_to do |format|
      format.json { head :forbidden, content_type: 'text/html' }
      format.html { redirect_to main_app.root_url, notice: exception.message }
      format.js   { head :forbidden, content_type: 'text/html' }
    end
  end
Levitate answered 3/8, 2023 at 13:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.