ビットフィールドでユーザの権限管理

権限管理用のgemもいくつかあるみたいですが、今回は機能を自作してみました。

Rubyならビットフィールドを使わずともハッシュで事足りるんでしょうけど、
こっちのほうがちょっとスッキリするかも…？気持ち的にも。

## 目指すところ

特定の権限を持つユーザーにだけリンクを表示したい場合

``` app/views/foo/bar.html.erb
<% if current_user.permission?('hoge') %>
	<%= link_to 'link_to_hoge', hoge_path %>
<% end %>
```

特定の権限を持つユーザーだけアクセスできるようにしたい場合
(権限を持たなければ403エラー(Forbidden)にする)

``` app/controllers/hoge_controller.rb
class HogeController < ApplicationController
  include CheckPermission

  before_action :authenticate_user!
  before_action -> { check_role_permission!('hoge')  }

  .....

end
```

それから…

* ユーザーの種類（ロール）ごとに付加データを持たせたい
 * フォームでの表示名
 * DB保存用のロールID
 * ...
* 権限の変更を簡単にできるようにしたい
* 権限の種類が増えてきてもスッキリ書きたい
* ビット演算を使いたい！

## ビットフィールドまわりの説明

ロールは、各権限の有無を表す数値を持つ。(0b***はRubyの2進数表記)

* 管理者：`0b1111_1111_1111_1111`
* 一般　：`0b0010_1101_0001_0000`
* ...

各ビットは、権限の有無を表している。
0ビット目はユーザー情報閲覧権限、1ビット目はユーザー情報編集権限、
2ビット目は顧客データの閲覧権限…など。

指定したビットが立っているか（権限を持つか）調べたい場合は、
そのビットだけ1になっている値との`&`をとって0かどうかを調べればよい。
(`&`をとると、両方のビットが1のときのみ1,それ以外のビットは0になる。)

たとえば顧客データの閲覧権限(2ビット目)を持つか調べたい場合は、

```管理者
  0b1111_1111_1111_1111
& 0b0000_0000_0000_0100
-----------------------
  0b0000_0000_0000_0100 (!= 0)
```

```一般
  0b0010_1101_0001_0000
& 0b0000_0000_0000_0100
-----------------------
  0b0000_0000_0000_0000 (== 0)
```

といった具合。

## 作ってみる

### 用意するGem
[plataformatec/devise](https://github.com/plataformatec/devise)(認証機能)
[binarylogic/settingslogic](https://github.com/binarylogic/settingslogic)(定数管理)

### 権限設定を書く
settingslogicの設定ファイルに。
一旦形を作っておけば後からどんどん追加、変更ができる。
桁数が多くなってきたら4ビットぐらいで区切ると見やすい。

`<<`はシフト演算子で、指定した数だけビットを右にシフトしてくれる。
たとえば、`1<<2` => `0b0000_0001 << 2` => `0b0000_0100`

```config/application.yml
# 権限設定
permission:
  #reserved:        <%= 1 << 7  %> # 予約
  announcement:     <%= 1 << 6  %> # お知らせ記事投稿
  item_write:       <%= 1 << 5  %> # 商品情報編集
  item_read:        <%= 1 << 4  %> # 商品情報閲覧

  customer_write:   <%= 1 << 3  %> # 顧客情報編集
  customer_read:    <%= 1 << 2  %> # 顧客情報閲覧
  user_write:       <%= 1 << 1  %> # ユーザー情報編集
  user_read:        <%= 1 << 0  %> # ユーザー情報閲覧

# ロール設定
role:
  admin:
    name:       管理者                # ロール表示名
    id:         10                   # ロールID
    permission: <%= 0b0111_1111 %>   # 権限設定値
  normal:
    name:       一般ユーザー
    id:         20
    permission: <%= 0b0101_0000 %>
```

### ユーザーテーブルにロールIDを持たせる

``` db/migrate/2014**********_add_user_to_role_id.rb
class AddUserToRoleID < ActiveRecord::Migration
  def change
    add_column :users, :role_id, :integer
  end
end
```

### 権限チェックできるようにする
ついでに付加データも取れるようにしたり。

```app/models/user.rb
  # ロールの権限をチェックする
  def permission?(key)
    role = __role_data
    permission = role.present? ? role[1]['permission'] : 0
    return (permission & Settings.permission[key]) != 0    # 上で説明したビット演算！
  end
  # ロール名を取得する
  def role_name
    role = __role_data
    role.present? ? role[1]['name'] : nil
  end
  # ロールIDを収得する
  def role_id
    role = __role_data
    role.present? ? role[1]['id'] : -1
  end

  private
  # ロールのデータを取得する
  def __role_data
    Settings.role.detect { |_, val| val['id'] == self.role_id }
  end
```

最初に書いたこれができるようになった！

``` app/views/foo/bar.html.erb
<% if current_user.permission?('hoge') %>
	<%= link_to 'link_to_hoge', hoge_path %>
<% end %>
```

### 権限によるアクセス制限ができるようにする

いろんなcontrollerファイルで使うと思うので、concernsにモジュールとして作成する。

```app/controllers/concerns/check_permission.rb
module CheckPermission
  extend ActiveSupport::Concern

    # 権限があるかをチェック
    def check_role_permission!(key)
      render file: 'public/403.html', status: :forbidden unless current_user.permission?(key)
    end

end
```

最初に書いたこれができるようになった！

``` app/controllers/hoge_controller.rb
class HogeController < ApplicationController
  include CheckPermission

  before_action :authenticate_user!
  before_action -> { check_role_permission!('hoge')  }

  .....

end
```

ちなみに、特定のアクションのみ制限したいときは、

```
  before_action -> { check_role_permission!('hoge')  }, only: [:new, :edit]
```
といった感じで書ける。

## 参考
権限管理用のgemについて
[rails 4 対応の認可の gem](http://blog.n-z.jp/blog/2013-10-08-rails4-authorization.html)
deviseについて
[Rails4 にて Devise でユーザー登録・ログイン認証・認可の機能を追加](http://easyramble.com/devise-on-rails.html)
settingslogicについて
[Railsで定数を一元管理する(settingslogic)](http://qiita.com/hilotter/items/45616c83704a15532987)
concernsについて
[ActiveSupport::Concern でハッピーなモジュールライフを送る](http://www.techscore.com/blog/2013/03/22/activesupportconcern-%E3%81%A7%E3%83%8F%E3%83%83%E3%83%94%E3%83%BC%E3%81%AA%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%83%A9%E3%82%A4%E3%83%95%E3%82%92%E9%80%81%E3%82%8B/)