Ruby on Railsで開発をしていると、Enumの使い方や挙動について曖昧なまま、なんとなくの雰囲気で使用していませんか?
Enumはとても便利ではありますが、きちんと理解せずに使っていると以下のような問題が発生しかねません。
- データの整合性が取れなくなる
- 将来の拡張性が制限される
- コードの可読性・保守性の低下
この記事を最後まで読めば、Enumの適切な使い方や注意点を理解し、根拠を持って使いこなせるようになります。
この記事を書いているぼくは実務経験1年。独学で未経験から従業員300名以上の自社開発企業へ転職しました。実務ではVue.jsとRailsを毎日書いています。
Enumとは?
Enumとは、カラムに格納されている整数を人間が読みやすい文字列や記号にマッピングできる機能です。
これによりデータベース内では整数として効率的に保存しつつ、コード上ではわかりやすい名前で扱うことができます。
基本的な使い方
Enumを使用するには、モデル内でenum
メソッドを呼び出します。
以下は、User
モデルでstatus
カラムをEnumとして定義する例です。
class User < ApplicationRecord
enum status: { inactive: 0, active: 1, suspended: 2 }
end
このように定義することで、以下のメソッドが自動生成されます。
user.active?
: ユーザーがactiveかどうかを確認user.active!
: ユーザーのstatusをactiveに変更User.active
: activeなユーザーを全て取得
なぜEnumを使うのか?
コード上ではユーザーのステータスを 0, 1, 2 の数値で表すよりも、inactive, active, suspended といった文字列で表現する方が圧倒的にわかりやすいです。
# 1 が何を表しているか分からない
if user.status == 1
# ステータスがアクティブの場合の処理
end
# どのような条件式か一目瞭然!
if user.status == :active
# ステータスがアクティブの場合の処理
end
一方でデータベースのstatus
カラムには 0, 1, 2の整数を格納した方が効率的です。
inactive, active, suspended といった文字列を格納するよりも、数値で管理した方が検索時のパフォーマンスが高速だからです。
つまり、Enumを使用することでコードの可読性向上とデータベースの効率的な利用を両立できるのです。
Enumを使う手順
ではここから実際にEnumを使う際の手順を一連の流れで見ていきます。
必要なのは以下の2ステップです。
- Enum として使用するカラムを追加する
- モデルファイル内で Enum を定義する
順番に説明します。
1. Enum として使用するカラムを追加する
まずは Enum として使用するカラムを追加します。
今回は冒頭で紹介した、User
テーブルにstatus
カラムを追加する想定で進めます。
$ rails g migration AddStatusToUsers status:integer
生成したマイグレーションファイルを編集し、デフォルト値とNOT NULL制約を付与します。
class AddStatusToUsers < ActiveRecord::Migration[7.1]
def change
add_column :users, :status, :integer, default: 0, null: false
end
end
そしてマイグレーションファイルを実行します。
$ rails db:migrate
これでカラム追加は完了です。
2. モデルファイル内で Enum を定義する
次に、冒頭で紹介したようにUser
モデルのstatus
カラムを Enum として定義します。
class User < ApplicationRecord
enum status: { inactive: 0, active: 1, suspended: 2 }
end
これで準備完了です!
以下のように使用できます。
user = User.create(status: :active)
user.active? # => true
user.inactive! # statusを'inactive'に変更
User.active # activeなユーザーを全て取得
注意点
上記で記載したハッシュではなく、文字列もしくはシンボルの配列形式でも定義することができます。
# ハッシュで定義
class User < ApplicationRecord
enum status: { inactive: 0, active: 1, suspended: 2 }
end
# 文字列の配列で定義
class User < ApplicationRecord
enum status: ["inactive", "active", "suspended"]
end
# シンボルの配列で定義
class User < ApplicationRecord
enum status: [:inactive, :active, :suspended]
end
ですが、この書き方は仕事で使う際はあまり推奨できません。
理由は次の2点です。
- 可読性が低い
- 0 から始まる連続した値しか割り当てられない
以下のように、実際の現場では連番ではダメなケースも多いです。
# 一部の数字に特別な意味を持たせるケース
class Order < ApplicationRecord
enum status: {
pending: 0,
processing: 1,
shipped: 2,
delivered: 3,
cancelled: 9 # キャンセルされた注文を特別に9で表現
}
end
# 外部APIの仕様に合わせた定義が必要なケース
class User < ApplicationRecord
enum role: {
guest: 0,
user: 1,
moderator: 5,
admin: 10
}
end
# 将来的な拡張を見越してあえて間隔を空けるケース
class Article < ApplicationRecord
enum status: {
draft: 0,
review: 5,
published: 10,
archived: 15
}
end
もちろん「配列で定義してしまうとかならず問題が生じる」というわけではないです。
ですが、ハッシュで定義する癖をつけた方が無難なのでおすすめです。
Enumを使う際の注意点
Enumは便利な機能ですが、使用する際にはいくつかの注意点があります。
- デフォルト値の設定
- 値の重複
- 順序変更
- 国際化(i18n)対応
- 名前の衝突
それぞれ解説します。
1. デフォルト値の設定
Enumを使用するカラムにはデフォルト値を設定することがベストプラクティスとされています。
nullを許容してしまうと、以下の問題が発生してしまうからです。
- Enumのメソッド(例:
status.active?
)が予期せぬ動作をする - アプリケーションロジックでNULL値の特別な処理が必要になる
そのため、マイグレーションファイルでカラム追加をする際に、デフォルト値とNOT NULL制約を設定することが強く推奨されます。
class AddStatusToUsers < ActiveRecord::Migration[7.1]
def change
# デフォルト値とNOT NULL制約を必ず付与する
add_column :users, :status, :integer, default: 0, null: false
end
end
2. 値の重複
同じ整数値を複数の状態に割り当てるのは絶対にNGです。
予期しない動作を引き起こしてしまうので、細心の注意を払いましょう。
# 良い例
enum status: { inactive: 0, active: 1, suspended: 2 }
# 悪い例(0が重複している)
enum status: { inactive: 0, active: 0, suspended: 1 }
3. 順序変更
Enumに状態を追加したい場合、順序を変更してしまうとデータの整合性が保てません。
新しい状態を追加する際は、既存の順序を変更せずに末尾に追加します。
# 変更前
enum status: { inactive: 0, active: 1 }
# 良い変更(末尾に追加)
enum status: { inactive: 0, active: 1, suspended: 2 }
# 悪い変更(順序の変更)
enum status: { suspended: 0, inactive: 1, active: 2 }
プロジェクトによっては、将来的に状態を末尾以外に追加したくなる可能性に備えて、最初から間を空けてEnumを定義することもあります。
# 変更前(あらかじめ番号に間隔を設けている
enum status: { inactive: 10, active: 20 }
# 変更後(順序は変わるが既存のデータの整合性は損なわれない)
enum status: { suspended: 5, inactive: 10, active: 20 }
4. 日本語(i18n)対応
Enumの状態を日本語で表示したいケースもあるはずです。
こういったケースでは、以下のようにi18nを使用して日本語に対応させることができます。
ja:
enums:
user:
status:
inactive: "無効"
active: "有効"
suspended: "停止済み"
5. 名前の衝突
複数のカラムでEnumを使用する場合、名前の衝突が起きる可能性があります。
衝突していなくても、似たような意味合いの単語を使ってしまうと状態がわかりにくいです。
こういった場合、プレフィックスを使用することで、可読性の向上が可能です。
# prefix をつけずに status と visibility カラムを Enum として定義する
class Post < ApplicationRecord
enum status: { draft: 0, published: 1 }
enum visibility: { public: 0, private: 1 }
end
# 意味が似ているので status と visibility のどちらの判定をしているのか分かりにくい
post.published?
post.public?
# _prefix: true をつけて status と visibility カラムを Enum として定義する
class Post < ApplicationRecord
enum status: { draft: 0, published: 1 }, _prefix: true
enum visibility: { public: 0, private: 1 }, _prefix: true
end
# どのカラムの判定をしているのかが分かりやすい
post.status_published?
post.visibility_public?
おわりに
今回はRuby on RailsのEnumについて解説しました。
EnumはRuby on Railsの開発ではほぼ間違いなく使われますのでしっかりマスターしておきましょう。
また、以下の記事ではワンランク上のRailsエンジニアになりたいと考えている方向けにおすすめの技術書を紹介しています。
こちらの記事もぜひ読んでみてください。
コメント