【Rails】Rails 8.0 における enum の基本的な使い方

## はじめに
学習内容のアウトプットのため、Rails 8.0 で簡単なプロジェクト管理アプリを作成していたところ、`enum`定義の仕方が変更されていたことに気づかず、エラー解決まで時間を要してしまいました。そこで、今回学んだ事を踏まえ、改めて`enum`の使い方をおさらいし、備忘録として残しておきたいと思います。

#### この記事でわかること
- Rails 8.0 以降の`enum`の基本的な使い方


#### この記事の対象者
- `enum`って何？と思った初学者の方
- 今まで通り`enum`を使っていたはずなのにエラーが出てしまった方


## 列挙型(`enum`)とは？
列挙型(`enum`)は、あらかじめ定義された一連の定数値を名前付きで管理するデータ型です。
Railsでは、モデルに`enum`を定義することで、ステータスや種類などの限られた選択肢しか取らない値に意味のある名前を付けて管理することができます。

## 動作概要
### 前提
Taskモデルを例に説明していきます。tasksテーブルは、タスクの名前である title カラムと、タスクの状態を表す status カラムを持っています。

マイグレーションファイルは以下のようになっていて、`enum`で管理する status カラムは **integer型** を指定します。
```ruby
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
Rails 8.0 では、**第一引数**に**対象とする属性名**を指定します。
```ruby: task.rb
class Task < ApplicationRecord
  enum :status, { incomplete: 0, in_progress: 1, complete: 2 }
end
```

- #### Rails 7.2 以前
```ruby: task.rb
class Task < ApplicationRecord
  enum status: { incomplete: 0, in_progress: 1, complete: 2 }
end
```

私は最初こちらの記述で書いていたために下記のようなエラーが発生していました。
```shell
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) 
```

:::note warn
Rails 7.2 では、キーワード引数の使用による列挙型の定義は非推奨との事で警告メッセージが表示されるようです。
:::


### 使い方
- #### 状態に応じたデータを取得する場合
`enum` を利用すると、指定した値に対応するデータを取得するスコープが自動生成されます。
```ruby
# モデル名.キー名
Task.incomplete     # => status が "incomplete" のレコードを取得
Task.in_progress    # => status が "in_progress" のレコードを取得
Task.complete       # => status が "complete" のレコードを取得
```

- #### `enum` の定義を参照する場合
statuses のように属性名を**複数形**にして使用すると `enum` の定義を確認でき、キー（シンボルまたは文字列）と対応する値（整数）を使った処理や一覧の取得も簡単に行えます。
```ruby
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
```

- #### オブジェクトの状態を判定する場合
**「キー名 + ?」** で生成されるメソッドを使用することで、特定のレコードの状態を判定できます。
```ruby
task = Task.create!(title: "first task", status: "in_progress")

task.incomplete?
=> false
task.in_progress?
=> true
```

- #### 現在の状態を取得する場合
`enum` の値は、整数、文字列、またはシンボルとして設定可能で、モデル内ではキー名が文字列として保持されます。
```ruby
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** はデータベースに保存される値を取得するものです。
```ruby
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** オプションを使うと、メソッド名の衝突を避けることができ、名前の可読性を向上させることができます。これは特に同じモデル内で似たような名前のメソッドが多い場合に有効です。
```ruby: task.rb
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? のようなメソッドが生成されます。
```ruby
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` 周りの実装に悩んでいる方の助けになれば嬉しく思います！


## 参考URL
- [Rails 7.2のenum非推奨警告を回避する方法と修正ポイント](https://media.zenet-web.co.jp/entry/2024/07/01/085407)
- [【Rails】Enumを使いこなそう](https://zenn.dev/gottsu/articles/66b3ebe0f9e5a6)
- [ActiveRecord attribute methodまとめ（その①: BeforeTypeCast編）](https://qiita.com/maabow/items/33ad5328b192e3adb49e)