enumを使う

今日はリファクタリングについて勉強しました。

そこでリファクタリングのなかの

enumを使ってリファクタリングをする方法を学びました。

 

enumとはなにか

enumとは複数の定数をまとめることができる型です。

今回はあとで、タスク管理のアプリのなかでenumを使う例を記述するつもりです。

 

具体的にどんなところでenumを使うかというと、

 

タスクを種類別に

if 0 => 私用

elsif 1 => 仕事

else 2 =>その他

とビューで設定して、そのタスクが私用なのか仕事用なのか、それともその他のものなのかというように種類別に分けてわかりやすいようにしてあるんですが

 

これだと問題が2つあります。

  • kindの0, 1, 2がそれぞれ何を意味するのか、ビューを見ないと分からない
  • kindの種類が増える度、新たにif文を足さなければいけない

 

こういう問題があるとコード量が増えてきた時にエラーが起こる原因になります。

 

そこでこの問題を解消するためには

enumをモデルで定義するんです。そうすれば解決します!

 

実際にこのアプリを例にとって見てみるとわかりやすいと思います。

 

実装例

 

先ほど説明したように、タスク管理アプリでenumを使ってみます。

 

enumを使う前のコード

enumを使用する前のコードを載せておきます。

 

コントローラー

app/controllers/tasks_controller.rb

class TasksController < ApplicationController

  def index
    @task = Task.new
    @tasks = Task.where('start_at > ?', Time.zone.now).order(start_at: :asc)
  end

  def create
    @task = Task.new(task_params)
    @task.save
    redirect_to tasks_path
  end

  def update
    @task = Task.find(params[:id])
    if @task.finished == false
      @task.finished = true
      @task.save
      redirect_to tasks_path
    else
      render :index, alert: '既にタスク「#{task.title}」は完了しています '
    end
  end

  private

  def task_params
    params.require(:task).permit(:title, :content, :start_at, :finish_at, :kind, :finished)
  end
end

 

コントローラはTasksテーブルに対応したTasksコントローラのみです。
indexアクション、createアクション、updateアクションが存在しています。

 

ビュー

app/views/tasks/index.html.haml

%nav.navbar.navbar-light{style: :"background-color: #e3f2fd;" }
  %a.navbar-brand 登録済みタスク一覧

= form_for @task, html: {class: 'form-group'} do |f|

  = f.label :kind
    = f.select :kind, [0, 1, 2], {}, class: 'form-control', placeholder: 0 

  = f.label :title
  = f.text_field :title, class: 'form-control', placeholder: "ここにタスク名を入力してください"

  = f.label :content
  = f.text_area :content, class: 'form-control', placeholder: "ここにタスクの詳細を入力してください"

  = f.label :start_at
  = f.datetime_select :start_at, ampm: :true, minute_step: 15, class: 'form-control'

  = f.label :finish_at
  = f.datetime_select :finish_at, ampm: :true, minute_step: 15, class: 'form-control'

  = f.submit '作成する', class: "btn btn-primary"

.table-responsive
  %h4.r-title-label{style: 'display: inline-block; margin-right: 20px; '}
  %table.table.table-striped.b-t.b-light
    %thead
      %tr
        %th 種類
        %th タスクの名前
        %th タスクの内容
        %th 開始時間
        %th 終了時間
        %th 状態
    %tbody
      = render @tasks

 

ページ上部の フォームから入力された情報は、Tasksコントローラを通じてTasksテーブルに保存されます。Tasksテーブルに登録されている各レコードは、部分テンプレートviews/_task.html.hamlを通じて描画されています。

 

ビューの部分テンプレート

app/views/tasks/_task.html.haml

%tr
  %td
    - if task.kind == 0
      私用
    - if task.kind == 1
      仕事
    - if task.kind == 2
      その他
  %td
    = task.title
  %td
    = task.content
  %td
    = task.start_at
  %td
    = task.finish_at
  %td
    - if task.finished?
      完了済
    - else
      未完了
      %br
      = link_to "完了にする", task_path(task), method: :patch, class: "btn btn-primary "

 

 

こんな感じのコードです。

どんな風に動くかというと、以下の画像をみてください。

 

f:id:lilybelly:20181015144733g:plain

こんな感じで、一番上の欄のkindというところで、

私用か仕事用かなどのタスクの種類を分けれるようにしてあります。

 

enumを使う

 

タスクを種類別に

if 0 => 私用

elsif 1 => 仕事

else 2 =>その他

とビューで設定して、そのタスクが私用なのか仕事用なのか、それともその他のものなのかというように種類別に分けてる部分は

 

 ↓app/views/tasks/_task.html.haml

%tr
  %td
    - if task.kind == 0
      私用
    - if task.kind == 1
      仕事
    - if task.kind == 2
      その他
  %td
    = task.title
  %td

 

 この部分ですね!

そしてそれを

app/views/tasks/index.html.haml

= form_for @task, html: {class: 'form-group'} do |f|

  = f.label :kind
    = f.select :kind, [0, 1, 2], {}, class: 'form-control', placeholder: 0

 

 indexのビューのform_forタグで配列形式でもってきて、

f.selectで選択フォーム を作っています。

 

ちなみに選択フォームはこんなやつです↓

        f:id:lilybelly:20181015150330p:plain

 

1.モデルにenumを定義する

 

 ↓app/views/tasks/_task.html.haml

%tr
  %td
    - if task.kind == 0
      私用
    - if task.kind == 1
      仕事
    - if task.kind == 2
      その他
  %td
    = task.title
  %td

 この条件分岐を消して、モデルにkindカラムとして定義してあげる

class Task < ApplicationRecord
  enum kind: [:individual, :work, :others]
end

 配列でとってるから、

0はinidividual(私用)

1はwork(仕事)

2はothers(その他)

になっていて、ビューでは「kind」でこの条件分岐を呼び出せます。

 

なのでさっきのif文を消して、kindに書き換えてあげましょう。

 

app/views/tasks/_task.html.haml

/if文を削除し、task.kindと書き換える/
%tr
  %td
    = task.kind
  %td
    = task.title
/以下省略/

 

 これで部分テンプレートの条件分岐のところはオッケーです。

 

ただ選択フォームはさっきの0、1、2のままなので、

ここも変更する必要があります。

 

app/views/tasks/index.html.haml

= form_for @task, html: {class: 'form-group'} do |f|

  = f.label :kind
    = f.select :kind, [0, 1, 2], {}, class: 'form-control', placeholder: 0

これを

  = form_for @task, html: {class: 'form-group'} do |f|

    = f.label :kind
    = f.select :kind, Task.kinds.keys, {}, class: 'form-control', placeholder: 0

に変更します。

 

これはなにをしてるかというと。

[0,1,2]が Task.kinds.keysとなっていますね。

 

実は、

enumを定義すると、[モデル名].[カラム名の複数形] のような形で、そのカラムに設定したenumを全て表示することができます。

 

なので

Task.kinds
=> {"individual"=>0, "work"=>1, "others"=>2}

Task.kindsと打つと2行目のように数字と値がハッシュで返されるようになります。

 

そこで「Task.kinds.keys」と打つとその中の、キーだけが取り出せます。

Task.kinds.keys
=> ["individual", "work", "others"]

 

 

これを入力フォームの f.select に引数として渡すことによって、選択肢を[0 ,1, 2]のように書くことを避けられるので、

ユーザーが選択するときにもわかりやすくて便利ですよね!

 

こんな風にenumをつかってコードをまとめることができました!

 

まとめ

  • enumとは複数の定数をまとめることができる型
  • 配列を数字ではなく、文字列で扱うことができる。
  • [モデル名].[カラム名の複数形] のような形で、そのカラムに設定したenumを全て表示することができる
  • [モデル名].[カラム名の複数形].keys のような形で、そのカラムに設定したenumのキーを一覧で表示することができる

最初は慣れないと思いますが、

リファクタリングのためにできるだけ使うようにしていきたいです。