英文原版: 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 冰痕 原创翻译 ----