本文へ移動

Railsのenumを使いこなす。状態管理を読みやすくする

Railsのenumを使いこなす。状態管理を読みやすくするの説明画像

Railsのenum、もっと使いやすくしたい

プログラミングをしていると、必ず出会う「状態管理」。
Railsではenumを使えば簡単に状態管理できますが、標準機能だけでは物足りないと感じたことはありませんか?

  • 状態に日本語ラベルを持たせたい
  • 状態ごとに説明文も持ちたい
  • 将来的に状態の追加・変更に強い設計にしたい

上記を一気に解決するのが、EnumBaseパターンです。
この記事では、普通のenumから一歩踏み込んで、実践で本当に使える状態管理を一緒に目指しましょう!

まずは復習:Rails標準のenumの素晴らしさ

Railsのenumは、手軽にマジックナンバーを排除できる仕組みです。

class User < ApplicationRecord
  enum status: { active: 0, inactive: 1, archived: 2 }
end

user.active!
user.inactive?
User.active

これだけで
- メソッドが生える
- スコープが作れる
- 可読性がグッと上がる(マジックナンバー解消)

最高ですよね。
でも、Rails標準のenumには一つだけ弱点があります。

状態に付加情報(表示名・説明など)を持たせられない。

これ、地味に辛いんです。

どんな問題が起こるのか?

例えば、ユーザー状態を日本語で表示したい場合。
標準のenumではビュー側でI18nを使うパターンが多いですが、
管理が煩雑になったり、状態追加時にコードと表示の対応がズレる危険もあります。

<%= t("enums.user.status.#{user.status}") %>

さらに「アクティブユーザーって何?」みたいな説明文も欲しくなると、もうRails標準の enum だけでは対応しきれなくなってしまいます。

解決策:EnumBaseパターンを使おう!

numBaseパターンでは、
「状態」を単なる数字ではなく、情報を持ったオブジェクトとして扱います。

これによって、
ラベル・説明・順番・色指定など
なんでも管理できる、リッチな状態管理が実現できます。

EnumBaseパターン 実装例

1. 共通親クラスを作成する

class EnumBase
  attr_reader :key, :value, :label, :description

  def initialize(key, value, label:, description: nil)
    @key = key
    @value = value
    @label = label
    @description = description
  end

  def to_s
    label
  end

  def self.all
    constants.map { |c| const_get(c) }
  end

  def self.find_by_value(value)
    all.find { |e| e.value == value }
  end

  def self.options_for_select
    all.map { |e| [e.label, e.value] }
  end
end

ここでは
- key:プログラム内での識別子
- value:DBに保存される数値
- label:人間向けの表示名
- scription:オプションの説明文

を持たせています。

2. 実際のEnumクラスを作る

class UserStatus < EnumBase
  ACTIVE   = new(:active, 0, label: 'アクティブ', description: '通常利用中のユーザー')
  INACTIVE = new(:inactive, 1, label: '非アクティブ', description: '一時停止中のユーザー')
  ARCHIVED = new(:archived, 2, label: 'アーカイブ済み', description: '退会したユーザー')
end

3. モデルとつなぐ

class User < ApplicationRecord
  enum status: {
    active: UserStatus::ACTIVE.value,
    inactive: UserStatus::INACTIVE.value,
    archived: UserStatus::ARCHIVED.value
  }

  def status_label
    UserStatus.find_by_value(status_before_type_cast)&.label
  end

  def status_description
    UserStatus.find_by_value(status_before_type_cast)&.description
  end
end

リファクタリング事例

もともと、ビューにこんなコードが乱立していたとします。

<% if user.status == 'active' %>
  アクティブ
<% elsif user.status == 'inactive' %>
  非アクティブ
<% elsif user.status == 'archived' %>
  退会済み
<% end %>

これを、EnumBaseパターンを使うと…

<%= user.status_label %>

一行で完結。
しかも状態追加・変更があっても、Enumクラス側だけ直せばOK!
ビューやコントローラーには一切影響を与えません。

さらに発展:Enumに色やソート順も持たせよう

たとえばEnumBaseをこう拡張すれば、

class EnumBase
  attr_reader :key, :value, :label, :description, :color, :sort_order

  def initialize(key, value, label:, description: nil, color: nil, sort_order: nil)
    @key = key
    @value = value
    @label = label
    @description = description
    @color = color
    @sort_order = sort_order
  end

  def self.all_sorted
    all.sort_by(&:sort_order)
  end
end

色付きバッジ表示や、状態リストの並び替えも自由自在にできちゃいます!

まとめ

項目 Rails標準enum EnumBaseパターン
管理できる情報 数字とシンボルのみ ラベル・説明・色・順序・何でも
拡張性 低い 高い
ビューコードのシンプルさ 普通 激シンプル
メンテナンス性 普通 高い

最後に

Rails標準のenumはすばらしい。
でも、それを自分たちの設計思想に合わせて進化させることもまた、Railsらしさだと思います。

状態管理の未来を、あなたの手でリッチにしていきましょう。

この記事をシェア

管理人プロフィール

あゆの塩焼きのプロフィール画像

あゆの塩焼き

現役Webエンジニア。日々の開発で得た学びや思ったことを記録しています。

詳しいプロフィールを見る

次の学びも、見逃さずに。

AIの実例・研究・ブログ・読書の更新を、RSSリーダーでまとめて購読できます。

RSSを購読する

Loading...