如何显示未经授权的can can访问错误

4
我正在使用Bootstrap,它有一个div class="alert notice",用于各种提示消息的类。
我还为评论添加了一个AJAX删除操作,并在其上添加了授权。当我尝试删除当前用户无权访问的评论时,操作不起作用 - 这是正确的。
但我希望发生错误时在Bootstrap样式的
中弹出一个错误消息,持续5至10秒,然后消失。
这是我的CommentsController.rb上的destroy操作。
  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

在同一个控制器中的私有方法中设置了@comment

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

这是我的 comments/destroy.js.erb

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

但是这并不影响未经授权的访问。

在我的ability.rb文件中,我有以下代码:

can :manage, Comment, user_id: user.id

当我尝试删除一条我无权访问的评论时,我的日志会显示以下内容:

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]:

这非常完美。

我想要做的就是在 Bootstrap 警告框中显示一个适当的错误信息,并在几秒钟后消失。

我该如何实现?

3个回答

4
首先,如果您使用的是 Cancan - 只需使用cancan:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  load_and_authorize_resource #this will set @comment by your ability and authorize action
  ...
end

这将会抛出CanCan::AccessDenied错误而不是ActiveRecord::RecordNotFound错误。

我们可以在ApplicationController中使用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

我使用PNotify库来显示弹出通知:http://sciactive.github.io/pnotify/ 它会在右上角显示错误信息,然后隐藏。 只需将其包含在您的项目中,您就可以像这样显示错误:

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

这段代码可以避免捕捉 ActiveRecord::RecordNotFound 错误,因为这是一种不好的做法。

更新

我忘了一件事!你必须删除 set_comment 方法和 before_action,或者像这样写:

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

这个回调函数覆盖了你代码中 load_and_authorize_resource 的 @comment 变量。 Cancan 通过 load_and_authorize_resource 加载资源,因此这个辅助函数是不必要的。 更新2 你还需要确保使用来自CanCanCommunity的最新版本 cancan,并且支持 rails4 ,因为原始版本不支持 rails4
只需在你的 Gemfile 中使用以下代码:
gem 'cancancan', '~> 1.9'

代替
gem 'cancan'

我喜欢这个......但是,它对我没有用。我仍然在我的日志中收到ActiveRecord :: RecordNotFound错误,并且前端没有任何反应。顺便说一下,我正在使用pnotify-rails gem - https://github.com/navinpeiris/pnotify-rails - marcamillion
我应该在 before_action :set_comment 之前还是之后执行 load_and_authorize_resource - marcamillion
你可以删除 before_action :set_commentload_and_authorize_resource 执行相同的任务。 - AnatoliiD
当我这样做时,授权根本不起作用 - 即我被允许删除我不应该删除的东西。这是否意味着我的 ability.rb 规则不足? - marcamillion
让我们在聊天中继续这个讨论 - AnatoliiD

2
在您的comments/destroy.js.erb文件中添加以下代码:
<% if defined?(@error) %>
  $('.alert.notice').text("<%= @error %>");
  $('.alert.notice').show();
  setTimeout(function() {
    $('.alert.notice').fadeOut('fast');
  }, 5000);
<% end %>

set_comment 方法更改为使用 find_by,以避免出现 RecordNotFound 异常:

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

然后在您的控制器中,您可以按照以下方式修改destroy操作:

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        

结束


这很漂亮和优雅,但对我没用。我仍然遇到了ActiveRecord错误。 - marcamillion
好的,请查看我的更新解决方案,以避免ActiveRecord错误。 - Alireza
@railsme 实际上帮我找出了问题所在。这是一个cancan问题。我需要使用cancancan。 - marcamillion

0
我将这个放在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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接