deviseを使ってみる

Ruby on Railsの勉強を始めました。
`devise`というユーザー認証系の便利なGemがあるとのことで、今回使用してみました。
ご参考にしていただければと思います。
# 準備
```
$ rails new rails_devise
$ cd rails_devise
$ bundle install --path=vendor/bundle
```
新規プロジェクトの作成と、初期のGemをインストールしました。
ついでにGitで管理もしたいので、Githubにリポジトリを作成しました。
（<a href="http://qiita.com/naoki85/items/4f44601f1365c18035f4" target="_blank">SourceTreeとGithubでGitの練習環境をつくる</a>）

```
$ git init
$ git remote add origin git@github.com:naoki85/rails_devise.git
$ git add .
$ git commit -m "first commit"
```
ここでリモートブランチに`README.md`を先に作成していると`push`するときに怒られるようです。
そのため、リモートのマスターブランチをマージします。

```
$ git pull origin master
# コンフリクトを解決
$ git add .
$ git commit -m "resolved conflict"
$ git push origin master
```
# deviseのインストール
`devise`をGemfileに追加します。

```diff:Gemfile
+ # Use Devise for Authentication
+ gem 'devise'
```
そしてインストールします。

```
$ bundle install
```

さて、ここから`devise`を実装していきます。
（参考資料：<a href="https://github.com/plataformatec/devise" target="_blank">公式のGithub</a>）

```
$ rails generate devise:install
```

インストールすると、何やら注意文が出てきました。
以下のページの執筆者の方が日本語訳してくださっておりましたので、そちらに従って対応します。
（<a href="http://qiita.com/cigalecigales/items/73d7bd7ec59a001ccd74" target="_blank">[*Rails*] deviseの使い方</a>）

その前に、`vendor/bundle`以下のファイル`.gitignore`に追加し、コミットさせないようにします。
（差分が見えづらくなるので。）

```diff:.gitignore
+ # Ignore bundler gems.
+ vendor/bundle
```
## デフォルトURLの指定
今回はローカル環境で試すだけなので、`localhost:3000`を指定します。

```diff:config/environments/development.rb
+ # mailer setting
+ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
```

## root_urlの指定
`http://localhost:3000`にアクセスしたとき呼ばれるページの指定をします。
<a href="http://qiita.com/cigalecigales/items/73d7bd7ec59a001ccd74" target="_blank">参考にさせていただいているページ</a>と同様、まだコントローラーも作成していなかったため、ここで作成します。

```
$ rails g controller Pages index show
```
これでコントローラーが作成できたので、`routes.rb`に記載します。

```diff:config/routes.rb
+ root 'pages#index'
+ get 'pages/show'
```
## Flashメッセージの設定
Flashメッセージが表示される場所を追記します。
以下の文そのままで良いようです。

```diff:views/layouts/application.html.erb
<body>
+  <p class="notice"><%= notice %></p>
+   <p class="alert"><%= alert %></p>
  <%= yield %>
</body>
```
## Viewの作成
> Deviseの導入で追加されるViewは、以下のコマンドを実行しなければデザインを変更できないので、デザインをカスタマイズするためにも実行します。
> <a href="http://qiita.com/cigalecigales/items/73d7bd7ec59a001ccd74" target="_blank">[*Rails*] deviseの使い方</a>

```
$ rails g devise:views
```
# モデルの作成
`devise`用のモデルを作成し、マイグレーションを走らせます。
（モデル名は任意です。）

```
$ rails g devise Admin_User
$ rails db:migrate
```

マイグレーションファイルは以下のようになっております。
デフォルトでコメントアウトされている部分もオープンにできますが、今回はライトに作りたいため、デフォルトのままでいきます。

```rb
class DeviseCreateAdminUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :admin_users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      t.integer  :sign_in_count, default: 0, null: false
      t.datetime :current_sign_in_at
      t.datetime :last_sign_in_at
      t.string   :current_sign_in_ip
      t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :admin_users, :email,                unique: true
    add_index :admin_users, :reset_password_token, unique: true
    # add_index :admin_users, :confirmation_token,   unique: true
    # add_index :admin_users, :unlock_token,         unique: true
  end
end
```
また、モデルも同様に、最初から使用できるモジュールと、任意で設定するモジュールがあります。

```rb:admin_user.rb
class AdminUser < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end
```
今回はデフォルトなので、初めから使用できる下記のモジュールを使用します。
（参考資料：<a href="http://babie.hatenablog.com/entry/20100729/1280381392" target="_blank">Railsの第4世代認証エンジンDeviseのREADMEを翻訳してみた</a>）

|モジュール名|役割|
|:-:|:-:|
| Database Authenticatable | ユーザーがサインインする時に認証するためにパスワードをデータベースに暗号化し保存します。この認証は POST リクエストまたはBasic認証を通して行われます。 |
| Registerable             |登録プロセスを通してサインアップを処理します。また、アカウントを編集・削除できるようにします。                                                      |
|Recorverble               | パスワードをリセットし、リセットの指示を送ります。                              |
| Rememberable             | ユーザーを記憶するために、保存されたクッキーから、トークンを生成・消去を扱います。|
| Trackable                | サインインのカウント・タイムスタンプ・IPアドレスを計測します。 |
| Validatable              |Eメールとパスワードによる確認を提供します。これは、オプションでカスタマイズできるので、あなた専用の確認を定義できます。|

ここまでで、とりあえず`rails s`でローカルサーバーを起動させ確認してみると、特に何もありません。（当たり前ですが。。。）
![スクリーンショット 2016-09-22 19.20.03.png](https://qiita-image-store.s3.amazonaws.com/0/118817/c8005033-0c18-ed65-7051-efdcb6650e57.png)
# ヘルパーの実装
ユーザーがログインしていなければリダイレクトさせるのは、下記を`pages_controller.rb`に追記します。

```diff:controllers/pages_controller.rb
class PagesController < ApplicationController
+  before_action :authenticate_admin_user!

  def index
  end

  # ...
```
再度、ページにアクセスしてみると、下記のようにリダイレクトされます。
![スクリーンショット 2016-09-22 20.16.11.png](https://qiita-image-store.s3.amazonaws.com/0/118817/dc08b21f-abbd-c370-84f3-318bc40dd400.png)
このビューは、`views/devise/sessions/new.html.rb`になります。
（`rails routes`で確認できます。）

とりあえず新規登録をしてみると、下図のようにログインまでしてくれます。
ただ、ログインしていないと無条件でリダイレクトされるのも微妙なので、`index`にはログインしていなくてもアクセスできるようにし、`show`にはログインしていないとアクセスできないようにします。
![スクリーンショット 2016-09-22 20.30.36.png](https://qiita-image-store.s3.amazonaws.com/0/118817/25170153-6469-aa7d-fa37-bcd52573e792.png)

### showだけログインしていないとアクセスできないようにする

```diff:controllers/pages_controller.rb
class PagesController < ApplicationController
-  before_action :authenticate_admin_user!
+  before_action :authenticate_admin_user!, only: [:show]

  def index
  end

  # ...
```

### ログイン後のリダイレクト先を変更する
`after_sign_in_path_for`や`after_sign_out_path_for`で上書きしてしまうようです。
必ず呼ばれる`application_controller.rb`に記述します。
（参考資料：<a href="http://tsumazuki.hatenadiary.jp/entry/2013/08/15/033130" target="_blank">ログイン後にマイページに飛ばす:Rails devise</a>）

```diff:controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

+  def after_sign_in_path_for(resource)
+    pages_show_path
+  end
end
```

### ログインとログアウトのリンクを設置する
ログインするのもログアウトするのもリンクがあると楽なので、簡単に設置してしまいます。

```diff:views/layouts/application.html.erb
<body>
+    <%= link_to 'Login', new_admin_user_session_path, method: :get %>
+    <%= link_to 'Logout', destroy_admin_user_session_path, method: :delete %>
    <p class="notice"><%= notice %></p>
    <p class="alert"><%= alert %></p>

    <%= yield %>
</body>
```
# ログインしている場合としていない場合でリンクを出し分ける
他にも便利なヘルパーがあり、`user_signed_in?`でログインしているかどうかの結果を返してくれます。
（ただし、今回はモデル名をAdmin_Userにしたため、下記のサンプルの中では`admin_user_signed_in?`となっています。）
これで、ログインとログアウトのリンクを出し分けたいと思います。

```diff:views/layouts/application.html.erb
-    <%= link_to 'Login', new_admin_user_session_path, method: :get %>
-    <%= link_to 'Logout', destroy_admin_user_session_path, method: :delete %>
+ <% if admin_user_signed_in? %>
+     <%= link_to 'Logout', destroy_admin_user_session_path, method: :delete %>
+ <% else %>
+     <%= link_to 'Login', new_admin_user_session_path, method: :get %>
+ <% end %>
```
# ログインしているユーザーの情報を取得
こちらは`current_user`でオブジェクトを取得できます。
（ただし、今回はモデル名をAdmin_Userにしたため、下記のサンプルの中では`current_admin_user`となっています。）
また、`user_session`で現在のセッション情報を取得できるようですが、初期状態は空っぽです。
（何も入れていないので当たり前ですが。。。）

```diff:views/pages/show.html.erb
-<h1>Pages#show</h1>
-<p>Find me in app/views/pages/show.html.erb</p>
+<h1>Hi!!<%= current_admin_user.email %>!!</h1>
+<p><%= debug(admin_user_session) %></p>
```
# 最後に
今回、モデル名をUserではなく、Admin_Userにしたため、基本ヘルパーの`user`の部分を`admin_user`にする必要がありました。
モデル名をUser以外にしようとしている方は要注意です。

ビューが少し寂しいので、Bootstrapで少し整えたいと思います。