[Ruby on Rails]Railsで診断機能を超簡単に実装する

## はじめに
本記事ではRailsで簡単に診断機能を作る方法を紹介します。
Viewページを質問の数だけ作成し、if文を使用して前の質問の回答結果に応じて質問を分岐させる簡単な設計です。結果は`db/seeds.rb`を使って診断結果を登録し、データベースから取得するようになっています。

:::note warn
注意
この実装方法では診断結果をDBに保存することはできません。
保存したい場合の実装方法も記事後半に載せているので、保存したい場合はそちらも合わせてお読みください
:::


## 診断の流れ
- 質問ページ (question1, question2, question3)
- URLパラメータを使って回答を次のページへ送る
- 最後に診断結果をデータベースから取得して表示

## 実装してみよう
### `Diagnosis`モデルの作成
既存のモデルに備え付けるでもいいですし、新しいモデルを作るでもどちらでも構いません。今回は新たに`Diagnosis`モデルを作成し、実装を進めていくこととします。
```bash:コマンドプロンプト
rails generate model Diagnosis result_key:string result_detail:text
rails db:migrate
```
カラムの役割について説明します。まず、`result_key`カラムは最終質問の結果を格納するカラムです。最後の質問の回答結果に「あ」〜「く」の記号(result_key)を割り振っています。もし、結果が「あ」だったらresult_keyに対応する診断結果をDBから取り出します。
続いて`result_detail`カラムは診断結果のテキストを格納するカラムです。例えば「診断の結果、あなたには〇〇がおすすめです！」のようなものです。

### `diagnoses_controller.rb`の作成
今度はコントローラーを作成します。作成時に一緒に`question1.html.erb`、`question2.html.erb`、`question3.html.erb`、`result.html.erb`も作成しています。
ややこしいですが、diagnosisの複数形は`diagnoses`なので`diagnoses`で実装しています
```bash:コマンドプロンプト
rails generate controller Diagnoses question1 question2 question3 result
```
### ルーティングの設定 `config/routes.rb`
設計上質問に行く前に一度1ページ挟みたいとかあれば適宜追記をお願いします！
```ruby:config/routes.rb
Rails.application.routes.draw do
  get "diagnoses/question1" #追加
  get "diagnoses/question2" #追加
  get "diagnoses/question3" #追加
  get "diagnoses/result" #追加
end
```

### 診断データの登録`db/seeds.rb`
```ruby:db/seeds.rb
Diagnosis.create([
  { result_key: "あ", result_detail: "あなたにオススメなのは ① です！" },
  { result_key: "い", result_detail: "あなたにオススメなのは ② です！" },
  { result_key: "う", result_detail: "あなたにオススメなのは ③ です！" },
  { result_key: "え", result_detail: "あなたにオススメなのは ④ です！" },
  { result_key: "お", result_detail: "あなたにオススメなのは ⑤ です！" },
  { result_key: "か", result_detail: "あなたにオススメなのは ⑥ です！" },
  { result_key: "き", result_detail: "あなたにオススメなのは ⑦ です！" },
  { result_key: "く", result_detail: "あなたにオススメなのは ⑧ です！" }
])
```
seedファイルに書き込めたら以下のコマンドを実行します。
```bash:コマンドプロンプト
rails db:seed
```
### 質問ページ`question.html.erb`の作成
以下はあくまで参考までに！自分のアプリの質問項目に変更して使ってね！
```ruby:question1.html.erb
<h1>診断スタート</h1>
<h2>選ぶなら？</h2>
<a href="/diagnoses/question2?answer=A">Aを選ぶ</a>
<a href="/diagnoses/question2?answer=B">Bを選ぶ</a>
```
```ruby:question2.html.erb
<% if params[:answer] == "A" %>
    <h1>Aを選んだあなた</h1>
    <h2>質問その2</h2>
    <a href="/diagnoses/question3?answer=C&prev_answer=<%= params[:answer] %>">Cを選ぶ</a>
    <a href="/diagnoses/question3?answer=D&prev_answer=<%= params[:answer] %>">Dを選ぶ</a>    
<% elsif params[:answer] == "B" %>
    <h1>Bを選んだあなた</h1>
    <h2>質問その2</h2>
    <a href="/diagnoses/question3?answer=E&prev_answer=<%= params[:answer] %>">Eを選ぶ</a>
    <a href="/diagnoses/question3?answer=F&prev_answer=<%= params[:answer] %>">Fを選ぶ</a>

<% end %>

```
```ruby:question3.html.erb
<% if params[:answer] == "C" %>
    <h1>Cを選んだあなた</h1>
    <h2>最終質問</h2>
    <a href="/diagnoses/result?answer=あ">オススメ１結果を見る</a>
    <a href="/diagnoses/result?answer=い">オススメ２結果を見る</a>
<% elsif params[:answer] == "D" %>
    <h1>Dを選んだあなた</h1>
    <h2>最終質問</h2>
    <a href="/diagnoses/result?answer=う">オススメ３結果を見る</a>
    <a href="/diagnoses/result?answer=え">オススメ４結果を見る</a>
<% elsif params[:answer] == "E" %>
    <h1>Eを選んだあなた</h1>
    <h2>最終質問</h2>
    <a href="/diagnoses/result?answer=お">オススメ５結果を見る</a>
    <a href="/diagnoses/result?answer=か">オススメ６結果を見る</a>
<% elsif params[:answer] == "F" %>
    <h1>Fを選んだあなた</h1>
    <h2>最終質問</h2>
    <a href="/diagnoses/result?answer=き">オススメ７結果を見る</a>
    <a href="/diagnoses/result?answer=く">オススメ８結果を見る</a>
<% end %>
```

### 診断結果を取得するresultアクション`app/controllers/diagnoses_controller.rb`の作成
診断結果を取得するためのロジックをここに記述します。
```ruby:diagnoses_controller.rb
def result
  @diagnosis = Diagnosis.find_by(result_key: params[:answer])

  unless @diagnosis
    flash[:alert] = "診断結果が見つかりませんでした"
    redirect_to root_path
  end
end
```

### 診断結果ページ`result.html.erb`の作成
ここに診断結果を表示します。
```ruby:result.html.erb
<div id="diagnosis">
  <% if @diagnosis.present? %>
    <h2>診断結果</h2>
    <p><%= @diagnosis.result_detail %></p>
  <% else %>
    <p>診断結果が見つかりません。</p>
  <% end %>
</div>
```
以上です！お疲れ様でした！

## 追記(診断結果をDBに保存したい人向け)
上記のやり方では、View上で`params[:answer]`を渡しているため、診断結果はデータベースに保存されません。
単純に一時的に表示させたい場合は問題ありませんが、もし診断結果をDBに保存する必要がある場合は、`result`のパラメータを`Controller`に渡し、適切にデータを保存する必要があります。以下にそのやり方を示します。

### 変更点：①`routes.rb`の修正
診断結果を`POST`で送信し、Controllerで処理するようにするため、POST ルートを追加します。
```ruby:routes.rb
Rails.application.routes.draw do
  get "diagnoses/question1"
  get "diagnoses/question2"
  get "diagnoses/question3"

  # 追加(診断結果を GET でも表示できるようにする)
  get "diagnoses/result", to: "diagnoses#result"

  # 追加(診断結果を POST で送信し、DB に保存する)
  post "diagnoses/result", to: "diagnoses#result"
end
```

### 変更点②:`diagnoses_controller.rb`の修正
診断結果を`POST`で送信し、診断履歴をDBに保存するように修正。
`question3`アクションと`result`アクションを大きく変更しています
```ruby:diagnoses_controller.rb
def question3
  @question = "最終質問"
  @options = []

  session[:previous_answers] ||= []
  session[:previous_answers] << params[:answer] if params[:answer].present?

  case params[:answer]
  when "C"
    @message = "Cを選んだあなた"
    @options = [
      { text: "オススメ１結果を見る", value: "あ" },
      { text: "オススメ２結果を見る", value: "い" }
    ]
  when "D"
    @message = "Dを選んだあなた"
    @options = [
      { text: "オススメ３結果を見る", value: "う" },
      { text: "オススメ４結果を見る", value: "え" }
    ]
  when "E"
    @message = "Eを選んだあなた"
    @options = [
      { text: "オススメ５結果を見る", value: "お" },
      { text: "オススメ６結果を見る", value: "か" }
    ]
  when "F"
    @message = "Fを選んだあなた"
    @options = [
      { text: "オススメ７結果を見る", value: "き" },
      { text: "オススメ８結果を見る", value: "く" }
    ]
  else
    flash[:alert] = "無効な選択です"
    redirect_to diagnoses_question1_path
  end
end

def result
  if request.post?
    @diagnosis = Diagnosis.find_by(result_key: params[:answer])

    if @diagnosis.nil?
      flash[:alert] = "診断結果が見つかりませんでした"
      redirect_to root_path and return
    end

    # 診断履歴をDBに保存（過去の回答履歴も記録）
    if user_signed_in?
      DiagnosisHistory.create(
        user: current_user, 
        diagnosis: @diagnosis, 
        previous_answers: (session[:previous_answers] || []).join(", ")
      )
    end

    # 診断後はセッションをリセット
    session[:previous_answers] = []

    redirect_to diagnoses_result_path(answer: params[:answer])
  else
    @diagnosis = Diagnosis.find_by(result_key: params[:answer])

    if @diagnosis.nil?
      flash[:alert] = "診断結果が見つかりませんでした"
      redirect_to root_path and return
    end

    render :result
  end
end

```
- `POST`リクエストを受け付けて診断結果を処理できるようにした
- `DiagnosisHistory.create`を追加し、診断結果をデータベースに保存できるようにした
- `redirect_to diagnoses_result_path(answer: params[:answer])`を追加し、診断後に `GET`で結果ページを表示するようにした

###  変更点③：`question3.html.erb`の修正
診断結果の選択を`GET`ではなく`POST`で送信するように修正。
```ruby:question3.html.erb
<h1><%= @message %></h1>
<h2><%= @question %></h2>

<% @options.each do |option| %>
  <%= form_with url: diagnoses_result_path, method: :post, local: true do %>
    <%= hidden_field_tag :answer, option[:value] %>
    <%= submit_tag option[:text] %>
  <% end %>
<% end %>
```
###  変更点④：`DiagnosisHistory`モデルの追加
DiagnosisHistory をデータベースで管理するために、モデルを作成し、マイグレーションを適用する必要がありました...
以下のコマンドを実行して、DiagnosisHistory モデルを作成します。
```bash
rails generate model DiagnosisHistory user:references diagnosis:references previous_answers:text
rails db:migrate
```
Userモデルなどある場合はリレーションも忘れず行ってください！

## まとめ
ルーティングではpost "diagnoses/result" を追加、ControllerはPOSTで診断結果を受け取り、DB に保存。質問ページ (question3.html.erb)	はPOSTでフォーム送信し、診断結果ページ (result.html.erb)で@diagnosisを使って表示するという流れで実装しました。