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?
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'