はじめに
学習内容のアウトプットのため、Rails 8.0 で簡単なプロジェクト管理アプリを作成していたところ、enum
定義の仕方が変更されていたことに気づかず、エラー解決まで時間を要してしまいました。そこで、今回学んだ事を踏まえ、改めてenum
の使い方をおさらいし、備忘録として残しておきたいと思います。
この記事でわかること
- Rails 8.0 以降の
enum
の基本的な使い方
この記事の対象者
-
enum
って何?と思った初学者の方 - 今まで通り
enum
を使っていたはずなのにエラーが出てしまった方
列挙型(enum
)とは?
列挙型(enum
)は、あらかじめ定義された一連の定数値を名前付きで管理するデータ型です。
Railsでは、モデルにenum
を定義することで、ステータスや種類などの限られた選択肢しか取らない値に意味のある名前を付けて管理することができます。
動作概要
前提
Taskモデルを例に説明していきます。tasksテーブルは、タスクの名前である title カラムと、タスクの状態を表す status カラムを持っています。
マイグレーションファイルは以下のようになっていて、enum
で管理する status カラムは integer型 を指定します。
class CreateTasks < ActiveRecord::Migration[8.0]
def change
create_table :tasks do |t|
t.string :title, null: false
t.integer :status, default: 0, null: false
t.timestamps
end
end
end
定義
modelファイルで定義します。
Rails 8.0 では、第一引数に対象とする属性名を指定します。
class Task < ApplicationRecord
enum :status, { incomplete: 0, in_progress: 1, complete: 2 }
end
class Task < ApplicationRecord
enum status: { incomplete: 0, in_progress: 1, complete: 2 }
end
私は最初こちらの記述で書いていたために下記のようなエラーが発生していました。
irb(main):001:0> Task.create!(title: "Sample Task", status: :incomplete)
app/models/task.rb:2:in `<class:Task>': wrong number of arguments (given 0, expected 1..2)
Rails 7.2 では、キーワード引数の使用による列挙型の定義は非推奨との事で警告メッセージが表示されるようです。
使い方
enum
を利用すると、指定した値に対応するデータを取得するスコープが自動生成されます。
# モデル名.キー名
Task.incomplete # => status が "incomplete" のレコードを取得
Task.in_progress # => status が "in_progress" のレコードを取得
Task.complete # => status が "complete" のレコードを取得
statuses のように属性名を複数形にして使用すると enum
の定義を確認でき、キー(シンボルまたは文字列)と対応する値(整数)を使った処理や一覧の取得も簡単に行えます。
Task.statuses
=> {"incomplete"=>0, "in_progress"=>1, "complete"=>2}
Task.statuses.keys
=> ["incomplete", "in_progress", "complete"]
Task.statuses.values
=> [0, 1, 2]
Task.statuses[:incomplete]
=> 0
「キー名 + ?」 で生成されるメソッドを使用することで、特定のレコードの状態を判定できます。
task = Task.create!(title: "first task", status: "in_progress")
task.incomplete?
=> false
task.in_progress?
=> true
enum
の値は、整数、文字列、またはシンボルとして設定可能で、モデル内ではキー名が文字列として保持されます。
task1 = Task.create!(title: "task1", status: 0)
task1.status
=> "incomplete"
task2 = Task.create!(title: "task2", status: "incomplete")
task2.status
=> "incomplete"
task3 = Task.create!(title: "task3", status: :incomplete)
task3.status
=> "incomplete"
値を文字列ではなく数値で取得したい場合は以下を使用します。
- [属性名]_before_type_cast
- [属性名]_for_database
- read_attribute_before_type_cast()
- read_attribute_before_type_cast()
[属性名]_before_type_cast は型変換前の値を取得し、[属性名]_for_database はデータベースに保存される値を取得するものです。
task1.status_before_type_cast
=> 0
task1.status_for_database
=> 0
# 引数には文字列かシンボルを渡す
task1.read_attribute_before_type_cast(:status)
=> 0
task1.read_attribute_for_database("status")
=> 0
# 前者と後者の違いは、存在しない属性を指定したときに"nil"になるか"例外"が発生するかどうか
task1.read_attribute_before_type_cast(:other_status)
=> nil
task1.other_status_before_type_cast
=> "NoMethodError"
prefix や suffix オプションを使うと、メソッド名の衝突を避けることができ、名前の可読性を向上させることができます。これは特に同じモデル内で似たような名前のメソッドが多い場合に有効です。
class Task < ApplicationRecord
enum :status, { incomplete: 0, in_progress: 1, complete: 2 }, prefix: :status
# 下記も同様
enum :status, { incomplete: 0, in_progress: 1, complete: 2 }, prefix: :true
end
これにより、status_complete? のようなメソッドが生成されます。
task = Task.create!(title: "task", status: "complete")
task.status_complete?
=> true
task.status_incomplete?
=> false
Task.status_complete # => status が "complete" のレコードを取得
suffix の場合も同様に、complete_status? といった具合で使用できます。
この prefix と suffix は同時に使うことも可能です。
なお、Rails 7.2 以前は、_prefix や _suffix のように _
を付けていましたが、Rails 8.0 ではエラーになるので注意しましょう。
おわりに
今回は Rails 8.0 における enum
の基本的な使い方を振り返り、備忘録として記事をまとめました。初めての記事ということもあり、読みにくい箇所があったかもしれませんが、温かく見守っていただけると幸いです。また、内容に誤りがあったり、enum
の発展的な活用方法をご存じの方がいれば、ぜひご意見をいただきたいです。
この記事が、新しく Rails を学び始めた方や、新しいバージョンで enum
周りの実装に悩んでいる方の助けになれば嬉しく思います!