devise でメールアドレスのみでユーザー登録を行い、パスワードを後から設定する方法


[devise](https://github.com/plataformatec/devise) を使って、

1. メールアドレスのみで初期登録を行う
2. トークン付きのパスワード登録用 URL を受信する
3. パスワードを入力し登録を完了する

という3ステップのユーザー登録を実現する方法です。

# 環境
* Ruby: 2.3.1
* Rails: 4.2.6
* Devise: 4.1.1

# サンプル
記事執筆時に作成したサンプルリポジトリを以下に置いています。

* [necojackarc-sandbox/email-only-signup-with-devise](https://github.com/necojackarc-sandbox/email-only-signup-with-devise)

# 注意
メールアドレスおよびパスワードのバリデーションについては、完全に省いた説明となっております。
実際に運用する際は、不正な値を登録させないよう、バリデーションを追加しておく必要があります。

また、password confirmation （パスワード確認用入力） も使用していません。
こちらを利用したい場合、[この記事の参考元である devise の wiki](https://github.com/plataformatec/devise/wiki/How-To:-Email-only-sign-up) か、日本語記事であれば [Devise confirmable用のテスト（フィーチャスペック）を書く（解説動画付き）](http://qiita.com/necojackarc/items/bd3286064ed3cd40db6b#comment-e19f7010687d2bf6bf50) などを参考にしてください。

# 手順
## 1. devise の導入
devise を公式ドキュメントに従い導入します。
ここでは `User` を devise で作成しています。

またこの時、

### confirmable を有効化
```ruby:app/model/user.rb  
devise :confirmable, :database_authenticatable, :registerable,
       :recoverable, :rememberable, :trackable, :validatable
```

```ruby:db/migrate/yyyyMMddHHmmss_devise_create_users.rb
## Confirmable
t.string   :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string   :unconfirmed_email # Only if using reconfirmable
```

### views/controllers をカスタム可能にする
```bash
$ rails g devise:views users
$ rails g devise:controllers users
```

```ruby:config/routes.rb
devise_for :users, controllers: {
  registrations: "users/registrations",
  confirmations: "users/confirmations"
}
```

という設定を行っています。

## 2. ユーザー登録画面の編集
パスワード入力が不要になるので、画面から削除します。

```erb:app/views/users/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= devise_error_messages! %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "users/shared/links" %>
```

画面から削除しただけだと、バリデーションが通らないので `password_required?` をオーバーライドします。

```ruby:app/models/user.rb
def password_required?
  super if confirmed?
end
```

このメソッドは [lib/devise/models/validatable.rb](https://github.com/plataformatec/devise/blob/4-1-stable/lib/devise/models/validatable.rb) に定義されているメソッドで、その名の通り passowrd 有無を判定するメソッドです。
以下のように devise 内部で利用されています。

> ```ruby:lib/devise/models/validatable.rb
> validates_presence_of     :password, if: :password_required?
> validates_confirmation_of :password, if: :password_required?
> ```

### 動作確認
`/users/sign_up` にアクセスしメールアドレスを入力します。

![Screen Shot 0028-06-14 at 19.03.00.png](https://qiita-image-store.s3.amazonaws.com/0/64733/fb4548b9-8478-9ec5-59c0-5f33ccf41129.png "Screen Shot 0028-06-14 at 19.03.00.png")

`Sign up`ボタンをクリックすると、メールアドレスの登録が成功しトップページへ遷移します。

![Screen Shot 0028-06-14 at 19.06.30.png](https://qiita-image-store.s3.amazonaws.com/0/64733/9965981e-526b-b2f4-65de-9c5449bfaa07.png "Screen Shot 0028-06-14 at 19.06.30.png")


このとき、devise のメッセージを表示するように設定を行っていれば、上記の通り確認メールが送られたという旨のメッセージが表示されます。

メールの内容は以下の通りです。

![Screen Shot 0028-06-14 at 19.10.57.png](https://qiita-image-store.s3.amazonaws.com/0/64733/58ffe765-115f-86ed-5695-b9129d4d075c.png "Screen Shot 0028-06-14 at 19.10.57.png")

以降では `Confirm my account` に遷移後、パスワードの登録とメールアドレスの確認を実施するようにします。

## 3. パスワード入力画面の作成
メール内に記載されている `Confirm my account` は `/users/confirmation?confirmation_token=abcdef` への GET リクエストですので、このアクションを修正します。

```ruby:app/controllers/users/confirmations_controller.rb
# GET /resource/confirmation?confirmation_token=abcdef
def show
  self.resource = resource_class.find_by_confirmation_token(params[:confirmation_token])
  super if resource.nil? || resource.confirmed?
end
```

このアクションに対応するパスワード入力画面を作成します。
画面には `hidden_field` で `confirmation_token` を埋め込み、パスワード入力欄を設置します。

```erb:app/views/users/confirmations/show.html.erb
<h2>Enter new password</h2>

<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name)) do |f| %>
  <%= f.hidden_field :confirmation_token %>

  <%= devise_error_messages! %>

  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "off" %>
  </div>

  <div class="actions">
    <%= f.submit "Submit" %>
  </div>
<% end %>

<%= render "users/shared/links" %>
```

入力した情報の submit 先は `users/confirmation` としています。

```ruby:config/routes.rb
devise_scope :user do
  patch "users/confirmation", to: "users/confirmations#confirm"
end
```

submit 先のアクションを作成します。

```ruby:app/controllers/users/confirmations_controller.rb
def confirm
  confirmation_token = params[resource_name][:confirmation_token]
  self.resource = resource_class.find_by_confirmation_token!(confirmation_token)

  if resource.update(confirmation_params)
    self.resource = resource_class.confirm_by_token(confirmation_token)
    set_flash_message :notice, :confirmed
    sign_in_and_redirect resource_name, resource
  else
    render :show
  end
end

private

def confirmation_params
  params.require(resource_name).permit(:password)
end
```

以上で設定完了です。

### 動作確認
先ほどの動作確認で受信したメールに記載されている URL へ移動します。

![Screen Shot 0028-06-14 at 20.00.19.png](https://qiita-image-store.s3.amazonaws.com/0/64733/af6e2b3a-6ee0-9b97-87f1-d18909b278da.png "Screen Shot 0028-06-14 at 20.00.19.png")


パスワードを入力して submit すれば登録完了です。

![Screen Shot 0028-06-14 at 20.01.02.png](https://qiita-image-store.s3.amazonaws.com/0/64733/a8a17095-aead-2088-3012-cc4d815527d6.png "Screen Shot 0028-06-14 at 20.01.02.png")

できました！

# まとめ
簡単に手順をまとめると以下の通りです。

1. `sign up` ページからパスワード入力欄を取り除く
2. パスワード無しでも登録可能になるよう model を編集する
3. メールアドレス確認用 URL にパスワード登録画面を作成する
4. パスワード登録 & メールアドレス確認のアクションを作成する

# 参考
* [How To: Email only sign up · plataformatec/devise Wiki](https://github.com/plataformatec/devise/wiki/How-To:-Email-only-sign-up)