CanCanCanを使ってみる

「<a href="http://qiita.com/naoki85/items/cabf7eee9564e56c3437" target="_blank">deviseを使ってみる</a>」の続きです。
ユーザーの権限付与に便利なCanCanCanを導入してみました。
公式Github→<a href="https://github.com/CanCanCommunity/cancancan#readme" target="_blank">https://github.com/CanCanCommunity/cancancan#readme</a>
# Gemfileの更新

```diff:Gemfile
+ gem 'cancancan'
```
```
bundle install
```
ちなみにbundle installと、updateの違いがいまいちよく分かっていませんでしたが、簡単に記述すると、

- install: Gemfile.lockを参照してGemをインストールする。
- update: Gemfile.lockを参照せずにインストールする。

詳しい解説は、下記の記事にて記載されていますが、基本的には`bundle install`を使用する方が良いようです。
（<a href="http://qiita.com/lasershow/items/1a048d03ddaaba98171e" target="_blank">bundle install と bundle updateの違いについて</a>）

# roleカラムの追加
`devise`でデフォルト作成されるテーブルを使用していたため、そこに`role`という権限を管理するカラムを追加します。
カラムの追加なので、マイグレーションファイルを作成します。

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

```rb
class AddRoleToAdminUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :admin_users, :role, :integer, null: false, default: 2
  end
end
```
権限のカラムはINT型でデータを保管するようにしました。
2の定義は後ほど実施していきます。

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

- <a href="http://blog.livedoor.jp/mondayish/archives/1008009.html" target="_blank">SQLite3でカラムを追加する</a>
- <a href="http://syaka-syaka.blogspot.jp/2015/06/postgresqladdcolumnafter.html" target="_blank">postgresqlでadd_columnのafterが効かない</a>

# モデルの修正
新しいカラムを作成したので、モデルを修正します。
`enum`を使用し、この文字列ならばこの数値をDBに入れるよう定義します。

```diff:admin_user.rb
+ 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`に修正しました。

```rb: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`としました。
こちらの記事が非常に詳しく記載しております。
<a href="http://qiita.com/umanoda/items/679419ce30d1996628ed" target="_blank">How to use CanCan / CanCanCan</a>
# テスト用にビューを修正
権限付与のテストのため、以下をビューに追記します。

```diff
+ <% 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?`を利用する際は、暗黙的に以下が呼ばれます。

```rb
def current_ability
  @current_ability ||= Ability.new(current_user)
end
```
私の場合ですが、今回`devise`にてAdminUserモデルを作成してしまった関係上、`current_user`ではなく、`current_admin_user`を引数にとってもらいたいです。

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

```diff: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](https://qiita-image-store.s3.amazonaws.com/0/118817/5c7491f0-4192-cc9f-08be-3e34ae44465e.png)
のみが表示されます。
ここで、`role`の値を1に変えると、（まだ編集機能などはないので、テーブルを直接書き換えて）
![スクリーンショット 2016-09-25 16.24.42.png](https://qiita-image-store.s3.amazonaws.com/0/118817/de7ae091-a9f2-e7f2-bb16-587caa13845a.png)
updateとreadの両方が表示されます。
1にはmanageにより全権限があるため、readでもupdateでもtrueが返されます。
# deviseで新規登録する際にroleも登録する
ここからはdeviseを使用している場合になります。
新しく`role`カラムを追加したので、ストロングパラメータに追加してあげる必要があります。

```diff: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→<a href="https://github.com/plataformatec/devise#readme" target="_blank">https://github.com/plataformatec/devise#readme</a>

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

```diff: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_for`の`select`を利用して、以下のようなHTML文を出力しています。
`enum`により、`admin`を渡せば1が、`member`を渡せば2がDBに入るようになっています。

```html
<select id="admin_user_role" name="admin_user[role]">
    <option value="admin">administer</option>
    <option value="member" selected>member</option>
</select>
```