Gem Keeping it Simple: Migrating to Pundit from CanCan 中文版

icemark · 2014年04月11日 · 最后由 flowerwrong 回复于 2014年05月29日 · 4227 次阅读

英文原版: http://blog.carbonfive.com/2013/10/21/migrating-to-pundit-from-cancan/

过去几年我们大多数 Rails 项目的权限管理都使用了 CanCan。当项目升级到 Rails4 时,我发现 CanCan 不能兼容 strong parameters。有一些补丁的方法可以让 CanCan 跑起来,但是感觉不对味。当然 CanCan 也没有得到太多人的喜爱,而且 Cancan 的项目代码库上很多迹象都表明项目不够活跃,趋向于无人维护。

我感觉是时候可以看看 Rails 权限管理方面有没有新的发现。

大约一年前 Jonas Nicklas 发布了 Pundit,目的就是要创建更简单更少"魔法"的权限管理方案。

这引起我强烈的共鸣!!!我不喜欢框架,特别是那些在背后做了太多或者太难理解工作的框架。我需要的是简单的,刚刚够用的东西,或者可以提供一个很好的壳,让我可以在上面添加我自己的东西,这也是非常棒的。

我决定要尝试一下 Pundit.

迁移到 Pundit

迁移到 Pundit 有以下几个步骤: I. Policies and Policy Specs

Pundit 从 Policy 对象中获取映射到 model 对象的认证规则。按照惯例,NotePolicy 保存的是 Note model 的规则。 我们需要手动把 Cancan 的 ability.rb 转为一个或者多个 Policies。

# Styled for brevity...
class NotePolicy < ApplicationPolicy
  def show?   ; true; end
  def create? ; true; end
  def update? ; record.user == user; end
  def destroy?; record.user == user; end
end

# And a top-level policy for setting defaults.
class ApplicationPolicy
  attr_reader :user,  # User performing the action
              :record # Instance upon which action is performed

  def initialize(user, record)
    raise Pundit::NotAuthorizedError, "Must be signed in." unless user
    @user   = user
    @record = record
  end

  def index?  ; false;                              end
  def show?   ; scope.where(id: record.id).exists?; end
  def new?    ; create?;                            end
  def create? ; false;                              end
  def edit?   ; update?;                            end
  def update? ; false;                              end
  def destroy?; false;                              end

  def scope
    Pundit.policy_scope!(user, record.class)
  end
end

这非常容易配合测试驱动开发

describe NotePolicy do
  subject { NotePolicy.new(current_user, note) }

  context "for a user" do
    let(:current_user) { build_stubbed :user }

    context "creating a new note" do
      let(:note) { Note.new }

      it { should     permit(:new)    }
      it { should     permit(:create) }
    end

    context "with a note someone else created" do
      let(:note) { build_stubbed :note, user: build(:user) }

      it { should     permit(:show)    }
      it { should_not permit(:edit)    }
      it { should_not permit(:update)  }
      it { should_not permit(:destroy) }
    end

    context "with a note that I created" do
      let(:note) { build_stubbed :note, user: current_user }

      it { should     permit(:show)    }
      it { should     permit(:edit)    }
      it { should     permit(:update)  }
      it { should     permit(:destroy) }
    end
  end
end

Tips: 推荐使用 FactoryGirl 的 build_stubbed .绝大多数情况下我们不需要持久化的实例,能不碰数据库就不碰。 II. Application Controller 通过 ApplicationController 让 Pundit 在所有的 controllers 生效

class ApplicationController < ActionController::Base
  include Pundit

  # Verify that controller actions are authorized. Optional, but good.
  after_filter :verify_authorized,  except: :index
  after_filter :verify_policy_scoped, only: :index

  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  private

  def user_not_authorized
    flash[:error] = "You are not authorized to perform this action."
    redirect_to request.headers["Referer"] || root_path
  end

  # ...
end

Tips: 通过第 5,6 行的 filters 来保证所有的 action 都进行认证,这和 CanCan 的 check_authorization 方法是类似的。这是可选的步骤,但是推荐加上

------------- 中间部分跳过,直接到结论 ----

我非常喜欢 Pundit,在我需要的场景中它都非常容易使用,也工作良好。Pundit 非常小,LOC 252,而 Cancan 是 1620,代码质量也很高,Code Climate 得分是 3.9.

最后,我选择增加少量几行代码,而放弃隐藏的复杂性(又名“Magic” )。我觉得这是一个合理的交换并让代码更具备可读性。在大多数情况下这是优点,对刚接触项目的新人就更为明显。Pundit 足够简单,任何开发人员都可以随时把它大卸八块来看看里面是咋回事。

我的结论是:Pundit 胜出!!!

最后,我们也非常感谢创造了 CanCan 的 Ryan Bates ,CanCan 陪伴我们走过了这么多年,也是 Jonas 创造 Pundit 的起点。我们都非常幸运地成为这样一个充满活力的开放源代码社区的一部分。

-- ruby-china 社区成员 icemark 冰痕 原创翻译 ----

当项目升级到 Rails4 时,我发现 CanCan 不能兼容 strong parameters。

我有一个项目用到了 cancan,升级到 rails4,没发现不兼容 strong parameters

#1 楼 @tylerlong load_resource,不过就省那几行代码用处实在有限...

之前看过,好像不错,之后打算用一下

很多人推荐 Pundit,就到楼主这里看看

需要 登录 后方可回复, 如果你还没有账号请 注册新账号