【Rails】enumで対応ステータスを更新する

自作アプリを作成しているときに、管理者機能として、フォームからのお問い合わせの対応ステータスを更新する機能があったらいいなと思い、調べながら実装してみたので、メモしておきます！

実装画面はこんな感じです👇
![Videotogif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215197/bfe2bc62-b139-b6ec-6f39-dd550aa53e4e.gif)

## 前提

**構成**（モデル等は生成済の前提で進めます）

- **MODEL (FormInquiry)**
    - t.integer "user_id"
    - t.text "content"
    - t.integer "response_status", default: 0
    - t.datetime "created_at"
    - t.datetime "updated_at"
- **CONTROLLER (form_inquiries)**
    - 問い合わせ一覧(index action)
    - 問い合わせ詳細(show action)
    - 対応ステータス更新（update action)
- **VIEWS**
    - 問い合わせ一覧（index.html）
    - 問い合わせ詳細（show.html）

**ライブラリ**
- enum_help（enumをI18n化するgem）
- devise（ユーザー/管理者認証）

**その他**
- 会員（エンドユーザー）をUser、管理者をAdminとしてそれぞれテーブルを作成しています。
- 会員からフォームで問い合わせを受け付けると、管理者の問い合わせ一覧画面に表示されます。
- 管理者は問い合わせ詳細画面を開き、対応ステータス（未対応/対応中/対応済）の更新を行えます。更新すると、フラッシュメッセージが表示され、問い合わせ一覧のステータスも更新されます。

**環境**
ruby 2.6.3
rails 5.2.6
OS：Linux(CentOS)
IDE：Cloud9

# 実装

**enumとは？**
「列挙型」という、整数が割り当てられた文字列を順に出力していく変数のことで、0を男性、1を女性のように、整数の値に何かしらの意味づけを行ってデータを管理することができます。「hashのkey部分の定数を保存すると、そのkeyに対応するvalueの整数が保存される仕組み」とのこと（[【Rails】 enumチュートリアル](https://pikawaka.com/rails/enum)より）

今回は、以下のような値で進めていくことにします。
```{ 0: outstanding（未対応）, 1: in_progress（対応中）, 2: closed（対応済）}```

## STEP1. enumの定義

👇 FormInquiryモデルの```response_status```カラムに対して、enumを定義します

```Ruby:app/models/form_inquiry.rb
belongs_to :user
enum response_status: { outstanding: 0, in_progress: 1, closed: 2 }
```

次に、enumを日本語化します。
👇 Gemfileに```enum_help```を追記して、保存したら```bundle install```します。

```:Gemfile
gem 'enum_help'
```

👇 英語名に対応する日本語の値を、```ja.yml```に定義します。

```R:config/locales/ja.yml
ja:
  time:
    formats:
      default: "%Y/%m/%d %H:%M"
  enums:
    form_inquiry:
      response_status:
        outstanding: "未対応"
        in_progress: "対応中"
        closed: "対応済"
```

👇 もしアプリケーションのデフォルト言語が日本語になっていない場合は、設定しておきます

```Ruby:config/application.rb
module App
  class Application < Rails::Application
    # 略
    config.i18n.default_locale = :ja
  end
end
```

## STEP2. コントローラーに記述
対応ステータスの更新はupdateアクションで行います。
更新できたら、問い合わせ詳細画面にリダイレクトし、フラッシュメッセージを表示するようにしています。

```Ruby:app/controllers/form_inquiry_controller.rb
class Admin::FormInquiriesController < ApplicationController
  before_action :authenticate_admin!
  before_action :set_form_inquiry, only: [:show, :update]

  def index
    @form_inquiries = FormInquiry.all.includes(:user).order(created_at: "DESC").page(params[:page]).per(10)
  end

  def show
    @user = User.find(@form_inquiry.user_id)
  end

  def update
    if @form_inquiry.update(form_inquiry_params)
      redirect_to admin_form_inquiry_path(@form_inquiry), notice: "対応ステータスを更新しました"
    else
      render :show, alert: "対応ステータスを更新できませんでした"
    end
  end

  private

  def form_inquiry_params
    params.require(:form_inquiry).permit(:response_status)
  end

  def set_form_inquiry
    @form_inquiry = FormInquiry.find(params[:id])
  end
end
```

## STEP3. ビューの記述

**問い合わせ詳細(show）**

👇 対応ステータスの更新は、問い合わせ詳細画面で行うため、```show.html.erb```にセレクトボックスを実装します（※該当部分のみ抜粋しています）

```:app/views/form_inquiries/show.html.erb
   <%= form_with model: [:admin, @form_inquiry], method: :patch, local: true do |f| %>
     <%= f.select :response_status, FormInquiry.response_statuses.keys.map {|k| [I18n.t("enums.form_inquiry.response_status.#{k}"), k]} %>
     <%= f.submit "変更" %>
   <% end %>
```

ここでは、enumの値（未対応/対応中/対応済）のセレクトボックスを作成するため、mapメソッド（配列の要素の数だけブロック内で処理を繰り返して、新しい配列を返すメソッド）を使用します。

ここで何をしているか確認しておくと、

- keysメソッドでハッシュオブジェクトからキーのみ配列の形で取り出している(```outstanding```/```in_progress```/```closed```）
- I18nのtranslateメソッド（```I18n.t```）でenumのキーの内容を翻訳している
- keysメソッドで取り出した値一つ一つに対しmapメソッドで処理し、二次元配列（配列の中に、配列が格納された形の配列のこと）を戻り値として返す

試しに```rails c```で返り値を見てみると、こうなっています👇
[3]はtranslateメソッドを使わなかった場合を一応出力してみた形です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215197/207c23ca-8e98-3116-b27e-826aa33c5a62.png)

二次元配列の2つ目の要素（```["対応中", "in_progress"]```)を選択して、検証ツールで見てみると、この二次元配列の１つ目の要素がoptionタグの表示部分に、２つ目の要素がvalueに入る仕組みであることがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1215197/03dc787f-2803-bff5-9d40-cd75ac774065.png)

これが他2つの二次元配列に対しても展開されて、セレクトボックスが生成されているようですね。


**問い合わせ一覧(index）**

👇 7〜13行目で、一覧に表示する対応ステータスをif文を使って分岐させています。
日本語化した定数の値を表示したいので、カラム名の末尾に```_i18n```を追記します。

```:app/views/form_inquiries/index.html.erb
    <table>
      <% @form_inquiries.each do |form_inquiry| %>
        <tr>
          <td><%= l form_inquiry.created_at %></td>
          <td><%= link_to form_inquiry.user.name, admin_user_path(form_inquiry.user.id) %></td>
          <td>
            <% if form_inquiry.response_status == 'outstanding' %>
              <%= form_inquiry.response_status_i18n %>
            <% elsif form_inquiry.response_status == 'in_progress' %>
              <%= form_inquiry.response_status_i18n %>
            <% else %>
              <%= form_inquiry.response_status_i18n %>
            <% end %>
          </td>
          <td>
            <%= link_to "詳細", admin_form_inquiry_path(form_inquiry.id), class:'btn btn-sm py-0 btn-outline-secondary' %>
          </td>
        </tr>
      <% end %>
    </table>
```

これで対応ステータスが更新できるようになりました！

keysメソッドやmapメソッドの部分は、個人的にまだ理解が難しいのですが、言語化することで理解が少し深まったように思います。何かお気づきの点があれば、ぜひご教示ください。

## 参考資料
- [【Rails】enumをI18n対応させるenum_helpが便利すぎた](https://kimuraysp.hatenablog.com/entry/2016/05/19/233144)
- [【Rails】 enumチュートリアル](https://pikawaka.com/rails/enum)
- [【Ruby】 mapメソッドの基礎から応用をマスターして、効率的なコードを書けるようにしよう！](https://pikawaka.com/ruby/map)
- [Rubyでハッシュオブジェクトからキーや値を取り出す方法【初心者向け】](https://techacademy.jp/magazine/19786)
- [【初心者向け】i18nを利用して、enumのf.selectオプションを日本語化する[Rails]] (https://azmengineer.hateblo.jp/entry/2019/06/20/101622)