【Rails】enumの使い方 - 便利なenumの何をどう使えば良いか

こんにちは!TECH DRIVEのTedです。

今回はenumについて、enumをどのように使えば良いかについてのご紹介をしたいと思います。

はじめに

enumとは列挙型のことを指し1つの識別子が複数の定数をもつことができる型のことである・・・と、enumについて調べると色々知ることができます。 しかし、初めてenumに触れる人にとってはイメージが掴みにくいかもしれません。

今回も、Railsを題材に使ってenumをどのように使えば効果的に使えるのかをみていきたいと思います。 少なくとも現場でコードを書かせていただいている私も同様の使い方をするので1つの例になると思います。

それから今回は、よくenumと合わせて使われるgem enum_helpも合わせて使っていきます。

enumを宣言してみる

enumはよくレコードの状態を表現するために使われます。

例えば、WordPressのようなCMSにおける記事を模したPostというモデルがあった場合に、投稿には下書きというプロセスがあるはずです。

このような場合にenumが力を発揮してくれたりします。

そして今回は次にあげるような仕様で進めたいと思います。

UserとPostは1対多の関係にあり。PostモデルはUserの投稿になります。

他のユーザーに公開するかどうかの状態をpostsテーブルのstatusというカラム持っていてこれをenumで管理して使い分ける場面です。

  • 下書き
  • 公開
  • 非公開

まずは、postsテーブルにstatusカラムを加えます。

マイグレートファイル

class AddStatusToPost < ActiveRecord::Migration
  def change
    add_column :posts, :status, :integer, default: 0
  end
end

モデルはこんな感じ

class User < ApplicationRecord
  # nameとかemailとかカラムを持っている

  has_many :posts
end

class Post < ApplicationRecord
  # bodyとstatusのカラムを持っている

  belongs_to :user
end

ここでPostのstatusというカラムに対してenumを宣言します

class Post < ApplicationRecord
  # bodyとstatusのカラムを持っている

  belongs_to :user
  enum status: { draft: 0, published: 1, unpublished: 2 } # ここでenumを宣言する
end

statusはinteger型で定義されたカラムなのでデータベース上に保存されるのは0か1か2のどれかになります。

上のマイグレートファイルで、statusカラムはdefault: 0として定義されているので、以下のようになるはずです。

  • draft:下書き(デフォルト)
  • publish:公開済み
  • unpublish:非公開

enumで使用するカラムはこのようにデフォルト値を設定しておくと扱いやすくなるでしょう。

statusをenumで宣言したので、この後プログラム上でstatusを扱う場合、それぞれの数字に割り当てられた状態(ここでいうdraft,published,unpublished)として扱うことができます。

enumで使えるようになるメソッド

enumを宣言することで次のようなメソッドが使用できるようになります。

これが本当に使い勝手が良いのです。

post = Post.create(user_id: 1, status: :draft, body: "投稿の中身")
# statusは status: 1 とかのintegerでも可能

# postの現在のstatusを出力
post.status # 出力 => draft
# postのstatusがdraftかどうかをbooleanで返す
post.draft? # 出力 => true
# postのstatusがpublishedかどうかをbooleanで返す
post.published? # 出力 => false
# statusがunpublishedに更新される
post.unpublished! 
# カラム名の複数形で状態のハッシュを取得
Post.statuses # 出力 => { draft: 0, published: 1, unpublished: 2 }

# スコープとしても使える
Post.published 
# publishedなpostレコードを検索する
# Post.where(status: :published)

現場で使われるenumでは上のようなメソッドを使用しているケースが多いのでこの辺りを押さえておくことをオススメします。

メソッドを応用した様々なユースケース

ここまでで、enumを宣言すると起こることがつかめるようになると思います。

しかし、enumの恩恵はわかりましたが、「何をどうやって使えば良いか」というのがピンとこないと思います。

ここでenumを実際に使ったケースを3点ご紹介します。

セレクトボックス

入力フォームで状態をセレクトボックスで選ばせたいとかあると思います。

以下のような(題材とは項目が違いますがイメージです)

f:id:travy:20190605161047p:plain

これはこんな風にやると実装できたりします。

<%= form_with(設定がある) do |form %>
  <%= form.select :post_config, Post.statuses%>
<% end %>

先ほどご紹介したメソッドの中にstatasesというのがありましたね。

今回はそれを使っています。

状態ごとのフィルタリング

欲しい状態のレコードだけを検索したい、みたいな状況でenumが活躍します。

enumで提供されているスコープをそのまま活用するだけなのですが、実際現場でよく使われる方法なので「こう使われる」というところだけ抑えていると良いでしょう。

例えば公開済みの記事だけを取得したいとしましょう。

posts = Post.published

これでpostsには公開済みの投稿が格納されます。

では、あるユーザーの公開済みの投稿を取得の場合

user = User.find(1)
user.posts.published

簡単に書くとこのようになります。

このようにenumのスコープは使い勝手が良い分、至る所で出会うことがあります。

更新処理

状態だけをサクッと変えたいとき、今回の場合だと下書きの記事をボタン1つで更新!としたい場合です。

statusカラムだけを更新したいけども、わざわざform_forとかでフォームを作って送信!みたいにviewに書くとちょっと仰々しいです。

なので、viewではaタグ(link_toで生成されるhtmlタグ)とかで更新用に作ったアクションにアクセスしてアクションの中でpublished!を読んでしまうと楽に実装できたりします。

# view
# PostsController#update_statusへのリンク
<%= link_to "公開", [publishアクションへのパス], method: :post %>
class PostsController < ApplicationController
  def update_status
    post = Post.find(params[:id])
    post.published! # ここでサクッと更新してしまう
    redirect_to xxx_path
  end
end

日本語化(i18n)

最後に、enumの日本語化についてここで少し書いておこうと思います。

先ほどご紹介した、セレクトボックスをそのまま実装すると、enumで宣言したdraftpublishedunpublishedが英語のまま表示されてしまいます。

またviewのページで post.statusみたいに書くと同様に英語で表示されることでしょう。

ここは次のように、i18n対応(日本語)にしておきたいところではあります。

  • draft => 下書き
  • publish => 公開
  • unpublish => 非公開

ここでgem enum_helpの出番です。このgemを使うことでenumで列挙した項目のi18n(日本語)対応ができるようになります。

次のようなステップで日本語化の準備をしていきます。

  1. app/config/locales以下に日本語化ファイルに設定を書き込む
# app/config/locales/ja.yml ← ここは読み込めるのであれば名前はなんでも良い
ja:
  enums:
    post:
      status:
        draft: 下書き
        published: 公開
        unpublished: 非公開
  1. 実際に使うとき enumを宣言したカラムを呼び出すときにカラム名の後に_i18nと書けばi18n対応ができます。

例えばPostモデルのstatusカラムの場合

post.status_i18n

となります。

さらに、先ほどのセレクトボックスも以下のように書き直すことができます。

form.select :status, Post.statuses_i18n.invert

と書くといい感じに日本語化されるでしょう。

Post.statuses_i18n
=> { "draft" => "下書き", "publish" => "公開", "unpublish" => "非公開" } 

invertについてはrubyのhashオブジェクトのメソッドになるので今回ご紹介はしませんが調べておくと良いでしょう。

gem enum_helpはenumを使用するときにはほぼ必須のgemになりますので覚えておくと良いでしょう。

まとめ

enumを宣言するだけで便利なメソッドやスコープが簡単に使えるようになるのは、enumの素晴らしい利点だと思います。

今回のような状態を扱う実装を行う際は「enumは使えないだろうか?」と検討してみるのは良いことだと思います。

今回は投稿の下書き、公開についてでしたが、例えばユーザーが管理者であるか一般のユーザーであるかを分けるため、のような使い方もよく見かけます。

enumを使いこなすことでコードがスッキリして良いコードを書く手助けになることだと思いますので是非使ってみてください。

PR

TECH DRIVE協賛企業のサークルアラウンド株式会社では、プログラマーの成長を加速させるためのトレーニングを行なっています。フロントエンド/バックエンド問わず各種バリエーションがございますので、ご興味がある方は是非以下のリンクより詳細をご覧ください。

Ruby Climbing

週1からはじめられる「Ruby」でWEB開発の基礎が習得できる塾です。現役のプログラミング講師&Rubyエンジニアがプログラミング入門からフレームワーク(Sinatra/Ruby on Rails)を使用した本格的なWEB開発の学習までをしっかりとサポートします。

ruby climbing

個別トレーニング

短期間でぐっと成長したい方は弊社主催の個別トレーニングがおすすめです。 トレーニング内容は、受講者の方の課題/要望をお伺いした上で、フルオーダメイドで作成させていただきます。 詳細は以下のリンクよりご確認ください。(応募者多数の場合には時間を別途ご用意する予定です)。

WEBプログラミング個別トレーニング

TECH DRIVEについて

TECH DRIVEは「技術者の成長を加速させる」をキーワードに都内で活動をしているコミュニティです。
TwitterやFacebookにて技術ネタやイベント情報の発信を行っていますので、ご興味があれば、いいねやフォローをお願いいたします。