「deviseを使ってみる」の続きです。
ユーザーの権限付与に便利なCanCanCanを導入してみました。
公式Github→https://github.com/CanCanCommunity/cancancan#readme
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に入れるよう定義します。
+ enum role: { admin: 1, member: 2 }
Abilityクラスの作成
CanCanCanでキモとなるAbilityクラスを作成します。
とはいえ、rails generete
を使用します。
rails g cancan:ability
これで、app/models/ability.rb
が作成されます。
デフォルトではinitialize
があるものの、特に記載がないため、今回の条件に合わせておきます。
なお、今回はadmin
とmember
の2つの権限を作り、admin
は全て実行可能、member
はリードオンリーなイメージとします。
2017-05-28 追記
ShoheiNakano様よりご指摘をいただいた項目を修正しました。
(今回はAdminUserモデルを使用しているため、AdminUser.new
になります。)
合わせて、引数もadmin_user
に修正しました。
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
を引数にとってもらいたいです。
その場合はオーバーライドします。
+ def current_ability
+ @current_ability ||= Ability.new(current_admin_user)
+ end
実際に試してみる
まずは、role
の値が2のユーザーで試すと、(member)
のみが表示されます。
ここで、role
の値を1に変えると、(まだ編集機能などはないので、テーブルを直接書き換えて)
updateとreadの両方が表示されます。
1にはmanageにより全権限があるため、readでもupdateでもtrueが返されます。
deviseで新規登録する際にroleも登録する
ここからはdeviseを使用している場合になります。
新しくrole
カラムを追加したので、ストロングパラメータに追加してあげる必要があります。
+ 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
そして新規登録の際の入力フォームに追記します。(今回は登録のみ修正します。)
+ <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_for
のselect
を利用して、以下のような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>