Edited at

CanCanCanを使ってみる

More than 1 year has passed since last update.

deviseを使ってみる」の続きです。

ユーザーの権限付与に便利なCanCanCanを導入してみました。

公式Github→https://github.com/CanCanCommunity/cancancan#readme


Gemfileの更新


Gemfile

+ gem 'cancancan'


bundle install

ちなみにbundle installと、updateの違いがいまいちよく分かっていませんでしたが、簡単に記述すると、


  • install: Gemfile.lockを参照してGemをインストールする。

  • update: Gemfile.lockを参照せずにインストールする。

詳しい解説は、下記の記事にて記載されていますが、基本的にはbundle installを使用する方が良いようです。

bundle install と bundle updateの違いについて


roleカラムの追加

deviseでデフォルト作成されるテーブルを使用していたため、そこにroleという権限を管理するカラムを追加します。

カラムの追加なので、マイグレーションファイルを作成します。

rails g migration add_role_to_admin_users role:integer

そして、生成されたマイグレーションファイルを以下のように修正し、rails db:migrateします。

class AddRoleToAdminUsers < ActiveRecord::Migration[5.0]

def change
add_column :admin_users, :role, :integer, null: false, default: 2
end
end

権限のカラムはINT型でデータを保管するようにしました。

2の定義は後ほど実施していきます。

余談ですが、SQLite、PostgreSQLでは、カラムを指定位置に追加することができないようです。


モデルの修正

新しいカラムを作成したので、モデルを修正します。

enumを使用し、この文字列ならばこの数値をDBに入れるよう定義します。


admin_user.rb

+ enum role: { admin: 1, member: 2 }



Abilityクラスの作成

CanCanCanでキモとなるAbilityクラスを作成します。

とはいえ、rails genereteを使用します。

rails g cancan:ability

これで、app/models/ability.rbが作成されます。

デフォルトではinitializeがあるものの、特に記載がないため、今回の条件に合わせておきます。

なお、今回はadminmemberの2つの権限を作り、adminは全て実行可能、memberはリードオンリーなイメージとします。

2017-05-28 追記

ShoheiNakano様よりご指摘をいただいた項目を修正しました。

(今回はAdminUserモデルを使用しているため、AdminUser.newになります。)

合わせて、引数もadmin_userに修正しました。


app/models/ability.rb

class Ability

include CanCan::Ability

def initialize(admin_user)
admin_user ||= AdminUser.new
if admin_user.admin?
can :manage, :all
end

if admin_user.member?
can :read, :all
end
end
end


権限には基本5種類あり、adminにはmanageを、memberにはreadを割り当てました。



  • read: 読み込み


  • create: 新規作成


  • update: 更新


  • destroy: 削除


  • manage: 全て

canの後に許可したい権限を指定し、第2引数で許可する場所を指定します。

今回はまだページもないので、allとしました。

こちらの記事が非常に詳しく記載しております。

How to use CanCan / CanCanCan


テスト用にビューを修正

権限付与のテストのため、以下をビューに追記します。

+ <% if can? :update, current_admin_user %>

+ <h1>You can update</h1>
+ <% end %>
+ <% if can? :read, current_admin_user %>
+ <h1>You can read member</h1>
+ <% end %>

can?メソッドで権限を確認し、その権限が第2引数で渡されたユーザーにあれば、trueを返します。

なお、このcan?を利用する際は、暗黙的に以下が呼ばれます。

def current_ability

@current_ability ||= Ability.new(current_user)
end

私の場合ですが、今回deviseにてAdminUserモデルを作成してしまった関係上、current_userではなく、current_admin_userを引数にとってもらいたいです。

その場合はオーバーライドします。


app/controllers/application_controller.rb

+ def current_ability

+ @current_ability ||= Ability.new(current_admin_user)
+ end


実際に試してみる

まずは、roleの値が2のユーザーで試すと、(member)

スクリーンショット 2016-09-25 16.25.38.png

のみが表示されます。

ここで、roleの値を1に変えると、(まだ編集機能などはないので、テーブルを直接書き換えて)

スクリーンショット 2016-09-25 16.24.42.png

updateとreadの両方が表示されます。

1にはmanageにより全権限があるため、readでもupdateでもtrueが返されます。


deviseで新規登録する際にroleも登録する

ここからはdeviseを使用している場合になります。

新しくroleカラムを追加したので、ストロングパラメータに追加してあげる必要があります。


app/controllers/application_controller.rb

+ before_action :configure_permitted_parameters, if: :devise_controller?


# 省略

+ private
+ def configure_permitted_parameters
+ devise_parameter_sanitizer.permit(:sign_up, keys: [:role])
+ end


今回のケースに限らず、カラムを追加したい場合はこのようにするようです。

deviseの公式Github→https://github.com/plataformatec/devise#readme

そして新規登録の際の入力フォームに追記します。(今回は登録のみ修正します。)


app/views/devise/registrations/new.html.erb

+ <div class="field">

+ <%= f.label :role %><br />
+ <%= f.select :role, {'administer' => 'admin', 'member' => 'member'}, {selected: 'member', include_blank: false}, {autofocus: 'true', class: 'form-control'} %>
+ </div>

form_forselectを利用して、以下のようなHTML文を出力しています。

enumにより、adminを渡せば1が、memberを渡せば2がDBに入るようになっています。

<select id="admin_user_role" name="admin_user[role]">

<option value="admin">administer</option>
<option value="member" selected>member</option>
</select>