deviseでユーザーIDとパスワードを使ってログインするようにしたい

## はじめに

railsで勉強用のアプリを作っていて、deviseを使ってユーザー認証周りを実装していました。
今回は勉強用ということでログイン周りはできる限りシンプルにしたいというのがこの記事の動機です。

## 環境

|key|value|
|:-:|:-:|
|OS|macOS High Sierra 10.13.6|
|rails|5.1.6|
|ruby|2.5.0|
|devise|4.5.0|

## deviseの設定

### セットアップ

基本的な設定は以下記事を参考にさせていただきました。

- [[*Rails*] deviseの使い方（rails5版）](https://qiita.com/cigalecigales/items/f4274088f20832252374)

### とりあえず動作確認

ちゃんとセットアップできたか確認しようと `users/sign_up` にアクセスするといきなりエラー。

```
undefined method `registration_path' for #<#<Class:0x007f936ab09c08>:0x007f936b049088>
```

まだ何も設定いじってないのに。。と思ったけど、railsサーバーを再起動させたら解決。幸先悪い。

参考: https://github.com/plataformatec/devise/issues/1773

### サインアップに必要な要素を変える

- in: 
  - ユーザーID(ログイン用)
  - ユーザー名(表示用)
- out:
  - メールアドレス

#### マイグレーションファイルの修正

```diff:db/migrate/XXX_devise_create_users.rb
- t.string :email,              null: false, default: ""
+ t.string :user_id, null: false, default: ""
+ t.string :name, null: false, default: ""
  t.string :encrypted_password, null: false, default: ""
```

#### viewファイルの修正

```diff:views/devise/registrations/new.html.erb

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

+ <div class="field">
+   <%= f.label :user_id %><br />
+   <%= f.text_field :user_id, autofocus: true, autocomplete: "user_id" %>
+ </div>

+ <div class="field">
+   <%= f.label :name %><br />
+   <%= f.text_field :name, autofocus: true, autocomplete: "name" %>
+ </div>
```

この状態で `users/sign_up` にアクセスしたら、表示状は問題なさそう。試しにユーザーを登録してみると...

```
NoMethodError in Devise::RegistrationsController#create
undefined method `email' for #<User:0x00007fdff2818f10>
```

どっかでメールアドレス使わないよー的な設定をしてあげなきゃいけなさそう。

#### Userモデルの修正

モデルにemailを利用しない設定をする。

```diff:models/user.rb
+ # No use email
+ def email_required?
+   false
+ end
+ 
+ def email_changed?
+   false
+ end
```

そしたら別のエラーが出た。

```
NoMethodError in Devise::RegistrationsController#create
undefined method `will_save_change_to_email?' for #<User:0x00007fdfe8c026d0> Did you mean? will_save_change_to_name? will_save_change_to_id? will_save_change_to_user_id?
```

deviseのバグっぽい？
`4.3.0`時点で発覚してるけど`4.5.0`でも直ってないのかな。
参考: https://github.com/plataformatec/devise/issues/4542

更にmodelに追記。

```diff:models/user.rb
+ def will_save_change_to_email?
+   false
+ end
```

これでもう一度サインアップしたところ動いた。
と思いきや、ログを見たらuser_idとnameがDBにインサートされてない模様。

```
19:26:20 rails.1   |   SQL (0.5ms)  INSERT INTO "users" ("encrypted_password", "created_at", "updated_at") VALUES (?, ?, ?)  [["encrypted_password", "$2a$11$zgNgzEm8gPeOICzt025hl.Ztow47sA2F.zu8OyOzJpqA0qRj2Sl6a"], ["created_at", "2018-09-30 10:26:20.997358"], ["updated_at", "2018-09-30 10:26:20.997358"]]
```

#### Controllerの修正

Controllerの修正も必要だった。

参考: https://qiita.com/uloruson/items/40154b4be19d1ac900f3

```diff:controllers/application_controller.rb
+ before_action :configure_permitted_parameters, if: :devise_controller?
+
+ protected
+
+   def configure_permitted_parameters
+     devise_parameter_sanitizer.permit(:sign_up, keys: [:user_id, :name])
+   end
```

確かに動いた。が、ここの仕組み全然理解できてない。要勉強。

### ログイン編

#### viewファイルの修正

```diff:views/devise/sessions/new.html.erb
- <div class="field">
-   <%= f.label :email %><br />
-   <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
- </div>
+ <div class="field">
+   <%= f.label :user_id %><br />
+   <%= f.text_field :user_id, autofocus: true, autocomplete: "user_id" %>
+ </div>
```

ログイン時は表示用の`name`はいらないので`user_id`だけ追加。
ログインしてみたら`Invalid Email or password.`って出た。これはまだemailの設定が残っていそうな予感。

#### Userモデルの修正再び

どうやらmodel内でこれをキーにするぜってのを上書きする必要がありそう。

```diff:models/user.rb
- devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
+ devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :authentication_keys => [:user_id]
```

`:authentication_keys => [:user_id]`が増分。

無事ログインも確認できた。

このタイミングで参考記事を見つけたのですが、ほぼ参考記事でよかった感ありますね。笑
参考: [deviseをユーザー名で登録・ログインできるように修正する](https://qiita.com/dayone80/items/ce04b21690f5f50a5cfa)

## おわりに

ユーザ編集ページとかまだ修正しなくちゃなところはありますが、とりあえずサインアップとサインインを実現できました。