Rails ~会員登録までの道のり~

## これまでのあらすじ
[前回の記事](https://qiita.com/tsunemiso/items/db17927fed0828b05a15)よりActionMailerの機能でメールを飛ばせることを検証しました。
今回は、メールアドレスを認証させるために仮ユーザ登録させてからユーザ登録させるための仮ユーザ登録機能を作ってみます。
ユーザ認証に関しては、[devise](https://github.com/plataformatec/devise)などのGemの利用がRailsでは、一般的（というか便利）らしいですが、Ruby・Railsの勉強をするために純粋にRuby・Railsで構築してきたいと思います。

## Top画面の作成

まずトップ画面を作成します。
```top_cotroller.rb```を作成します。

```bash:terminal
$ rails g cotroller top
```

```ruby:top_controller.rb
class TopController < ApplicationController
    def index
    end
end
```
indexアクションを作成し、```/views/top/index.html.erb```を作成します。
```config/routes.rb```にルートを追加する。

rootパスにtop#indexアクションを指定します。

```ruby:routes.rb
Rails.application.routes.draw do
    root 'top#index'

    # top_controller
    get 'top', to: 'top#index'
end
```

TOP画面はこんな感じにBootstrapを駆使して作りました。

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75277/4ca6210f-a566-e7c9-51c6-bc139a907cac.gif)

(管理者とかあるのはご愛嬌で...）

背景に動画を埋め込んで「イマドキ」っぽいおしゃれなWebサイトっぽくしました。

## 仮ユーザのModel作成

仮ユーザ(temp_user)のModel（DBのテーブル)を作成します。

```ruby:schema.rb
  create_table "temp_users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
    t.string "mail_address", null: false
    t.string "last_name", null: false
    t.string "first_name", null: false
    t.string "token", limit: 64, null: false
    t.datetime "expired_at", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["mail_address"], name: "index_temp_users_on_mail_address", unique: true
    t.index ["token"], name: "index_temp_users_on_token", unique: true
  end
```

仮ユーザ情報として、ログインに使用するmail_addressとユーザの氏名、仮ユーザを認証するためのtoken、仮ユーザ情報の有効期限のためのexpired_atで構成されています。

### 仮ユーザ登録までの流れ

1. TOP画面のsign onボタンを押下し、仮登録画面を表示。
2. 仮登録画面で氏名・メールアドレスを入力。
3. メールアドレス登録完了と同時に、本登録画面のurlを記載したメール送信。
4. 仮登録が完了した旨のメッセージ表示。

#### 1. TOP画面のsign onボタンを押下し、仮登録画面を表示。

[以前の記事](https://qiita.com/tsunemiso/items/edbc58becf55875c4fdb)で紹介したモーダルウィンドウを起動し、仮登録画面を表示しました。

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75277/4f3cb4df-65cb-54f2-2b5a-357eafce59ef.gif)

top_controller#sign_onで表示しています。
form_with用の@temp_userをnewしている以外は特別なことはしていません。

```ruby:top_controller.rb
class TopController < ApplicationController
    def sign_on
        @temp_user= TempUser.new
    end
end
```

#### 2. 仮登録画面で氏名・メールアドレスを入力。

temp_user.rbにバリデーションを追加しました。

```ruby:temp_user.rb
class TempUser < ApplicationRecord
    VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
    validates :last_name, presence: true
    validates :first_name, presence: true
    validates :mail_address, presence: true
    validates :mail_address, format: { with: VALID_EMAIL_REGEX }
end
```

氏名は必須。
メールアドレスは必須とメールアドレス正規表現によるフォーマットチェックをしています。

#### 3. メールアドレス登録完了と同時に、本登録画面のurlを記載したメール送信。

top#sign_onでformを表示し、formのsubmitでtop#createを読んでいます。
本処理は

* mail_addressがすでに使用されている(temp_userに登録済み)の場合は、UPDATE、未使用の場合は、INSERTをする。(UPSERT処理)
* UPDATE時、入力された姓・名・有効期限は後勝ちとする。
* tokenはランダム文字列で生成。

find_or_initialize_byで条件にformで入力されたmail_addressを指定しています。
find_or_initialize_byは取得できた場合、取得結果をModelに詰め、できない場合は、ただ単純にnewします。

その後、姓・名をセットします。

tokenですが、SecureRandom.urlsafe_base64でランダム文字列を作成しました。なお、重複登録を防ぐために、token = 生成文字列でtemp_userを検索し、存在した場合、再度生成しています。

有効期限はとりあえず現在日時をセットし、saveしています。

find_or_initialize_byで生成しているため、すでに存在している場合は、UPDATE・新規の場合はINSERTをrailsで判断しています。(便利ですね！！）

```ruby:top_controller.rb
    def create
        # 入力されたメールアドレスは登録済みか
        @temp_user = TempUser.find_or_initialize_by(mail_address: temp_users_params[:mail_address])
        # 入力された姓・名をセット
        @temp_user.last_name = temp_users_params[:last_name]
        @temp_user.first_name = temp_users_params[:first_name]
        # トークンを生成
        @temp_user.token = create_token
        # 有効期限に現在日時をセットする
        @temp_user.expired_at = DateTime.now

        respond_to do |format|
            # UPSERT実行
            if @temp_user.save
                format.js { @status = "success" }
            else
                format.js { @status = "fail" }
            end
        end
    end

    private
        def create_token
            token = nil
            loop do
                token = SecureRandom.urlsafe_base64
                    break if TempUser.find_by(token: token).nil?
            end
            return token
        end
```

#### 4. 仮登録が完了した旨のメッセージ表示。

成功した場合は、完了した旨のメッセージを表示しました。
これもModalで表示していますが、formのモーダルを消してから、完了のモーダルをshowするため
遅延実行しています。

```erb:create.js.erb
<% if @status == 'success' %>
    $('#top_modal').modal('hide');
    setTimeout(function(){
        $('#top_modal').html('<%= j(render 'completed_sign_on') %>');
        $('#top_modal').modal('show');
    }, 500);
<% elsif @status == 'fail' %>
    $('#sign_on_errors').html('<%= j(render 'layouts/error_messages', model: @temp_user) %>');
<% end %>
```

#### こんな感じ...

![ezgif.com-video-to-gif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75277/5fd09c56-977b-06e7-16ae-0d98c6a83fad.gif)

### 今後は…

これで仮登録までできました。
とりあえず、前回の記事の検証と合わせて、メールを飛ばすところまではつくりたいですね。
（こうしたほうがいいとか、ご意見あったらぜひぜひいただけると嬉しいです。)