[devise カスタマイズ]ユーザー登録完了前に（仮登録の状態で）他のカラムを追加させる

# [devise]ユーザー登録完了前に（仮登録の状態で）他のカラムを追加させる

## はじめに
ruby on railsのユーザー周り（会員登録など）のgemといえばdeviseですかね・・？
一瞬で会員登録とかができるのは物凄い便利です。

そんな便利な反面、カスタマイズするのにはかなり時間がかかります。
（今回もかなり時間がかかりました。）

今回実装するのは、以下のような機能です。

```rb:
# 実装したい内容
会員登録（emailとpassword）
↓
認証メール送信
↓
メールのリンクからプロフィール(今回はname)を追加するページへ
↓
プロフィールを入力して確認画面へ
↓
確認画面で送信
↓
会員登録完了（ログイン画面へ）
```
イメージ
![hoge.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/522280/e457e1b2-fc85-a8cb-e263-3c786b73c663.gif)

今回のissueは大きく二つ
>
- 会員登録を仮登録の状態で編集する
- 変更時に編集内容を確認するページを挟む


## 開発環境
```
- rails (6.0.3.2)
- ruby 2.6.5p114
- devise (4.7.2)
- letter_opener (1.7.0)

- mac Catalina 10.15.6
```


## セットアップ
前提として、以下の状態を想定しています

- deviseがインストールされている
- deviseのview,controller,modelが作成されている
- Useモデルが存在する(カラムはデフォルトにnameを追加)
- letter openerをgemにインストールしている(初期設定している)
- メール認証をtrueに変更している


## 使い方

今回のissueは大きく二つ
>
- **会員登録を仮登録の状態で編集する**   =>   （解決策） confirmation_tokenを保持して、最後にconfirmation_pathに引数として与える
- **変更時に編集内容を確認するページを挟む**   =>   （解決策）hidden_fieldを入れることでデータを保持する

1つ目のissueの肝は、**[confirmation_token]**
仮登録完了時に送られるメールをみるとわかりやすい

``` rb:/app/views/devise/mailer/confirmation_instructions.html.erb
<p>Welcome <%= @email %>!</p>

<p>You can confirm your account email through the link below:</p>

<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>

```

なんか、認証トークンとuserのデータを引数に持ってconfirmation_urlに入ったら仮登録→本登録完了になるらしい
つまり、
__これをやらずに__プロフィールを編集して、その後認証トークンを与えてconfirmation_tokenにアクセスして完了すればいい。

<br>
<br>
<br>
<br>
<br>

それでは実際にやってみる


### ①　ルーティングを設定

まず、追加するアクションは三つ。そのルーティングを行う。

> 今回はこの三つ。カッコの中身がアクション名（センスなくてすみません・・・）
[入力、確認、更新]
- 入力：プロフィール入力アクション(before_create)
- 確認：入力内容確認アクション(before_confirm)
- 更新：入力内容で更新するアクション(before_update)

```rb:config/routes.rb
Rails.application.routes.draw do

  devise_for :users, :controllers => {
    # コントローラーを見に行くようになる
    # 無しだと、そもそも見に行かずにデフォルトが実行される
    # (結論：deviseのコントローラーを変更したら記述が必須)
    :registrations => 'users/registrations',
    :sessions => 'users/sessions',
    :confirmations => 'users/confirmations'
  }

  devise_scope :user do
  #　ルーティングを指定
  #　アクションが追加されたらそのルーティングを指定する

    get "sign_in", :to => "users/sessions#new"
    get "sign_out", :to => "users/sessions#destroy"

    #プロフィール編集画面（仮登録状態）
    get "before_sign_up", :to => "users/registrations#before_create"
    #プロフィール編集内容確認画面（仮登録状態）
    post "before_sign_up_confirm", :to => "users/registrations#before_confirm"
    #プロフィール編集内容のアップデート処理（仮登録→本登録に）
    post "before_sign_up", :to => "users/registrations#before_update"

  end

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  root 'pages#index'
  get 'pages/show'
end
```

大きくやったことは二つ

>
- **device_for**でdeviseのcotrollerをカスタマイズ可能に
- **devise_scoope**で新しいアクションのパスを指定

### ② プロフィール編集アクションを追加（入力、確認、更新の三つ）

続いて、アクションの追加

>
 この三つ。カッコの中身がアクション名
- 入力：プロフィール入力アクション(before_create)
- 確認：入力内容確認アクション(before_confirm)
- 更新：入力内容で更新するアクション(before_update)

とりあえず、binding.pryで引数を見て回ったのと、superが表す内容をそれぞれ見て回った（[こんなサイト](https://rubydoc.info/github/heartcombo/devise/)で）

<br>
まずは
### **入力：プロフィール入力アクション(before_create)**

```rb:registrations_controller.rb
  # 仮登録状態のプロフィール入力画面（メール認証後のアクション）
  def before_create
    # 引数のresourceを使ってユーザーを取得
    @user =  User.find(params["resource"])
    @token = params["confirmation_token"]
  end
```

```erb:before_create.html.erb
<h2>プロフィールを登録</h2>
<%= form_for(resource, as: resource_name, url: before_sign_up_confirm_path(resource_name, confirmation_token: @token) ,html: {method: "post"}) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>
  <%= f.hidden_field :user_id, :value =>  resource.id %>
  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name, autofocus: true, autocomplete: "name" %>
  </div>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email",:readonly => true %>
  </div>
  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

```

デフォルトだったらparamsとかじゃなくてresourceで一発で呼べるんですけど、新しく追加したメソッドでresourceではとれない

>
余談
（厳密には、以下のような感じで取れるようにできるんですが、ログイン必須なので、仮登録では面倒そうだったので断念）
**registrations_controller.rb**に
**prepend_before_action :authenticate_scope! , only: [:before_create]**を追加

（余談の出来事）ということもあり、地道に実装することに。

1- 実は仮登録で、newされてるらしいので、newせずに@userにとってくる
2- confirmation_tokenは持ち続けたいので一旦@tokenへ
3- viewの**hidden_field**でuserのidを保持（これしなくても、paramsから取れる）


**確認：入力内容確認アクション(before_confirm)**

```rb:registrations_controller.rb
  # 仮登録状態のプロフィール入力完了画面(プロフィール入力後のアクション)
  def before_confirm
    @user =  User.find(params["user"]["user_id"])
    @user.name = params["user"]["name"]
    @token = params["confirmation_token"]
    if @user.valid?
      render :action => 'before_confirm'
      flash.now[:success] = '確認して完了してください'
    else
     render :action => 'before_create'
     flash.now[:alert] = '失敗しました'
    end
  end
```

```erb:before_confirm.html.erb
<h2>確認画面</h2>
<%= form_for(@user, url: before_sign_up_path(@user, confirmation_token: @token),html: {method: "post"}) do |f| %>
  <%= render "devise/shared/error_messages", resource: @user %>
  <%= f.hidden_field :user_id, :value =>  @user.id %>
  <%= f.hidden_field :email, :value =>  @user.email %>
  <%= f.hidden_field :name, :value =>  @user.name %>

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

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email",:readonly => true %>
  </div>
  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>

```

ここでsaveしないんですね。
@userに入れてみて、validかinvalidかを調べてるだけです。次のupdateで再び呼び出してからsaveします（ここがかなり効率悪いですよね・・・）


**更新：入力内容で更新するアクション(before_update)**

```rb:registrations_controller.rb
  # 仮登録状態のプロフィール入力内容更新処理
  def before_update
    @user =  User.find(params["user"]["user_id"])
    @token = params["confirmation_token"]
    @user.save
    if @user.valid?
      # ここが肝！
      #  confirmation_pathにuserデータと認証トークンを付与することで本会員登録される
      redirect_to confirmation_path(@user, confirmation_token: @token)
      flash[:success] = '確認して完了してください'
    else
      render :action => 'before_create'
      flash.now[:alert] = '失敗しました'
    end
  end
```





### ③ mailのパスを変更

最後に、
送られてくるメールの遷移先をを変更します。
`confirmation_url →　before_sign_up_url`
resourceもparamsで呼び出せるように追加　`@resource　→　resource:@resource`
（この辺深く理解してないのでミス多いかもです。すみません。）

```erb:app/views/devise/mailer/confirmation_instructions.html.erb
<p>Welcome <%= @email %>!</p>

<p>You can confirm your account email through the link below:</p>

<p><%= link_to 'Confirm my account', before_sign_up_url(resource:@resource, confirmation_token: @token) %></p>

```



## デモ
[ソースコード(github)](https://github.com/nobuosan/devise-test-app)
## 終わりに

gemってデフォルトで使う分にはすごい便利だけど、カスタマイズするにはgem自体をすごい理解しないといけないですよね。
特に初学者にはキツすぎる・・・

勉強不足で間違いなどあるかもしれませんが、その時は優しく指摘していただきたいです。
よろしくお願いします！


## 参考サイト
- [Rails初心者がdeviseのsessions#createを読み解く（途中）](https://qiita.com/after4649/items/e7b85c8e5cd6d92de1ed)
- [build_resourceとは](https://www.rubydoc.info/github/plataformatec/devise/Devise%2FRegistrationsController:build_resource)
- [Rails4のdeviseでユーザ登録→確認→完了画面を作成する](https://qiita.com/akasakas/items/9388ff6ba413fa0b6d97)