LoginSignup
46
40

More than 5 years have passed since last update.

CanCanCanを使ってみる

Last updated at Posted at 2016-09-25

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>
46
40
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
40