R 筆記 - Cancancan

之前的 DemoProject 中,架構都沒有大到需要用到角色來管理權限(可見多廢xd),所以就算知道可以用什麼來管,但也沒有直接的來寫過或接觸到,這幾天剛好有空就來好好研究,也順手記錄一下一些觀念。

先講個這個 gem 是幹嘛的:

  1. 定義不同使用者有不同角色,每個角色有不同權限
  2. 簡化 controller 的 code

Cancancan

Define Abilities

1
2
rails generate cancan:ability
#create app/models/ability.rb

ability.rb 定義每個角色擁有哪些權限,並在 view 或 controller 設定條件時,查看是否符合條件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# app > models > ability.rb

class Ability
include CanCan::Ability

def initialize(user, staff = nil)
alias_action :read, :update, to: :use # alias

if user.present? # additional permissions for logged in users (they can read their own posts)
can [:new, :create], Forum
cannot [:new], Comment
basic_read_only
else
if user.admin? # additional permissions for administrators
can :manage, :all
elsif staff && staff.role
eval("#{staff.class.name.underscore}_ability(staff)")
# eval 去 call 底下的方法
else
can :read, Post
end
end
end

protected

def basic_read_only
can :read, Forum
end

# 定義 Teacher 所擁有的權限
def teacher_ability(teacher)
...
end

# 定義 Student 所擁有的權限
def student_ability(student)
...
end
end

# can : 定義被允許的權限,後面接兩個參數,第一個是被允許的 action ,第二個是被允許的 object
# cannot : 同上,反之
# :manage : 指 所有的 action 方法,:all 表示所有的 object
# :read : 指 :index 和 :show
# :update : 指 :edit 和 :update
# :destroy : 指 :destroy
# :create : 指 :new 和 :crate

object 後可以設定有權限的其他情況(database columns)

1
2
3
4
5
can :update, Reseller do |reseller|
(reseller.id == user.id)
end

can :update, Reseller, id: user.id

自訂 Alias action

1
2
3
alias_action :index, :show, :to => :read
alias_action :new, :to => :create
alias_action :edit, :to => :update

自訂 method

1
2
3
4
5
protected

def basic_read_only
can :read, Forum
end

Authorizing controller actions

load_and_authorized_resource

1
2
3
4
5
6
7
8
9
10
11
class ProductsController < ActionController::Base
load_and_authorize_resource
def discontinue
# Automatically does the following:
# @product = Product.find(params[:id])
# authorize! :discontinue, @product
end
end

#指定 action
#load_and_authorize_resource :only => [:index, :show]

等於以下兩個方法:

  • load_resource
  • authorize_resource

load_resource

load_resource 自動加入 @instance,預設跟 Class 名稱相同,Article => @article

1
2
3
4
5
6
7
8
9
10
11
def ArticlesController < ApplicationController
load_resource

def new
# @article = Article.new
end

def show
# @article = Article.find(params[:id])
end
end

authorize_resource

將這個 Controller 加入權限的控制,並去 model 裡判斷權限是否有效

1
2
3
4
5
6
7
8
class TopicController < ApplicationController
authorize_resource :post #下方 index action 中的變數名稱

def index
@post = Topic.all
#預設為 @topic
end
end

Exception-Handling

若 user 無權限進入,cancancan 會噴出一個 CanCan::AccessDenied exception

authorize_resource or authorize! 會丟例外

1
2
3
4
5
#application_controller.rb

rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.messag #redirect_to
end

https://github.com/CanCanCommunity/cancancan