Rails7で導入された構文を使って、ActiveRecord::Enumを使ってみた

Railsのモデルで「(いわゆる)マスタを作るほどでもないけど数値に名前をつけたい」という時に、 ActiveRecord::Enum (enum) が使えます。
ActiveRecord::Enum

enum については、以下をはじめ、詳しく解説している記事がすでに存在します。
Railsのenumを使いこなす方法(翻訳)|TechRacho by BPS株式会社

 
ただ、自分で動かしてみないと分からないことがあるため、動かしてみた時のメモを残します。

なお、今回はRails7から enum に追加された新しい構文で書いてみます。
Rails 7のenumに新しい構文が導入(翻訳)|TechRacho by BPS株式会社

 
目次

 

環境

  • Rails 7.0.3
  • annotate 3.2.0
    • モデルにスキーマのコメントを追加して見やすくするために追加

 

enumを定義

モデルを作成

まずはモデルに integer 型の列を用意します。

今回は Article モデルに対し

  • release
  • payment

の integer 型の列を定義します。

% bin/rails g model Article title:string release:integer payment:integer

 
マイグレーションはこんな感じで、 integer 型の初期値を 0 とします。

class CreateArticles < ActiveRecord::Migration[7.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.integer :release, null: false, default: 0
      t.integer :payment, null: false, default: 0

      t.timestamps
    end
  end
end

 
マイグレーションします。

% bin/rails db:migrate

 

enumの定義をモデルに追加

第1引数に列名を、第2引数に各値と値に対応する名前を定義します。

enum :payment, { planing: 0, done: 1, pending: 2 }

 

動作確認

今回は Rails Console を使って enum の動作を確認します。

以下のようなデータをモデルへ追加します。

>> ringo = Article.create({title: 'りんご'})
   (2.3ms)  SELECT sqlite_version(*)
  TRANSACTION (0.1ms)  begin transaction
  Article Create (1.0ms)  INSERT INTO "articles" ("title", "release", "payment", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["title", "りんご"], ["release", 0], ["payment", 0], ["created_at", "2022-06-16 14:46:19.512793"], ["updated_at", "2022-06-16 14:46:19.512793"]]
  TRANSACTION (1.0ms)  commit transaction
=> #<Article:0x000000010e1dbc58 id: 6, title: "りんご", release: 0, payment: "planing", created_at: Thu, 16 Jun 2022 14:46:19.512793000 UTC +00:00, updated_at: Thu, 16 Jun 2022 14:46:19.512793000 UTC +00:00>

 

enum 列の更新

enum を定義すると、 ! 付きメソッドが自動生成されます。

以下の例では、pending! メソッドを使うことで、payment列を pending の値へと更新しています。

>> ringo.pending!
  TRANSACTION (0.1ms)  begin transaction
  Article Update (0.5ms)  UPDATE "articles" SET "payment" = ?, "updated_at" = ? WHERE "articles"."id" = ?  [["payment", 2], ["updated_at", "2022-06-16 14:46:31.508439"], ["id", 6]]
  TRANSACTION (2.4ms)  commit transaction
=> true

 
更新後の値を確認します。

>> ringo
=> #<Article:0x000000010e1dbc58 id: 6, title: "りんご", release: 0, payment: "pending", created_at: Thu, 16 Jun 2022 14:46:19.512793000 UTC +00:00, updated_at: Thu, 16 Jun 2022 14:46:31.508439000 UTC +00:00>

 
なお、Rails Console 上は pending という表記が見えますが、実際のテーブルには 2 という数値が入ります。

 

enum 列の状態確認

enum 列の状態確認をするには、こちらも自動生成される ? 付きメソッドを利用します。

以下の例では、 pending? メソッドを使い、 payment列が pending なのかを確認しています。

>> ringo.pending?
=> true

 

enum 列を文字列として取得

あるインスタンスの列の値を名前で取得したい場合、 enum 列名を使うと文字列が返ってきます。

>> ringo.payment
=> "pending"

 

enum 列を数値として取得

あるインスタンスの列の値について、文字列ではなくて数値を取得したい場合は、 列名_before_type_cast を使います。

>> ringo.payment_before_type_cast
=> 2

 

モデルのインスタンス以外で、enum 列の数値を取得する

***_before_type_cast はモデルのインスタンスで使えましたが、もし、インスタンスでない時に enum列の数値を取得したい場合は クラス.列[:値に対する名前] で取得できます。

>> Article.payments[:pending]
=> 2

 

scopeとして enum を使う

enumを定義するとscopeも自動生成されます。

例えば、 paymentpending のもののみを取得するような 処理をしてみます。

まずはデータの準備です。 paymentが done のものを用意します。

>> Article.create({title: 'とうふ', payment: Article.payments[:done]})
  TRANSACTION (0.1ms)  begin transaction
  Article Create (0.4ms)  INSERT INTO "articles" ("title", "release", "payment", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?)  [["title", "とうふ"], ["release", 0], ["payment", 1], ["created_at", "2022-06-16 14:48:02.847416"], ["updated_at", "2022-06-16 14:48:02.847416"]]
  TRANSACTION (1.0ms)  commit transaction
=> #<Article:0x000000010e290928 id: 7, title: "とうふ", release: 0, payment: "done", created_at: Thu, 16 Jun 2022 14:48:02.847416000 UTC +00:00, updated_at: Thu, 16 Jun 2022 14:48:02.847416000 UTC +00:00>

>> Article.count
  Article Count (0.7ms)  SELECT COUNT(*) FROM "articles"
=> 2

 
続いて pending をscopeとして使ってみます。

>> Article.pending
  Article Load (0.4ms)  SELECT "articles".* FROM "articles" WHERE "articles"."payment" = ?  [["payment", 2]]
=> [#<Article:0x000000010e2b3248 id: 6, title: "りんご", release: 0, payment: "pending", created_at: Thu, 16 Jun 2022 14:46:19.512793000 UTC +00:00, updated_at: Thu, 16 Jun 2022 14:46:31.508439000 UTC +00:00>]

 
値が取得できました。

 

別の enum で、同一の「値に対する名前」を定義

マイグレーションでは release 列も integer 型として用意していたため、こちらも enum として定義してみます。

ここで、同じ enum のキーを持っている場合はどのような挙動になるか確認します。

 

特にオプションを指定せず定義

まずは特にオプションを定義せずに、enumをモデルに定義します。

class Article < ApplicationRecord
  enum :payment, { planing: 0, done: 1, pending: 2 }
  enum :release, { planing: 0, done: 1, pending: 2 }  # 追加
end

 
値を取得してみるとエラーになりました。オプションを指定せずに定義した場合、値に対する名前が重複してはいけないようです。

>> Article.pending
/path/to/gems/activerecord-7.0.3/lib/active_record/enum.rb:301:in `raise_conflict_error': You tried to define an enum named "release" on the model "Article", but this will generate a instance method "planing?", which is already defined by another enum. (ArgumentError)

 

prefix を追加して定義

enumを定義する時のオプションとして、 prefixsuffix があります。

ここでは prefix オプション付きで定義してみます。

class Article < ApplicationRecord
  enum :payment, { planing: 0, done: 1, pending: 2 }
  enum :release, { planing: 0, done: 1, pending: 2 }, prefix: true  # 追加
end

 
prefixオプション付きで定義したことでエラーにならず、また、 pendingrelease_pending が定義されました。

>> Article.pending
  Article Load (0.5ms)  SELECT "articles".* FROM "articles" WHERE "articles"."payment" = ?  [["payment", 2]]
=> [#<Article:0x0000000113a20710 id: 6, title: "りんご", release: "planing", payment: "pending", created_at: Thu, 16 Jun 2022 14:46:19.512793000 UTC +00:00, updated_at: Thu, 16 Jun 2022 14:46:31.508439000 UTC +00:00>]

>> Article.release_pending
  Article Load (0.2ms)  SELECT "articles".* FROM "articles" WHERE "articles"."release" = ?  [["release", 2]]
=> []

 
他にも自動生成された release_planing を使ってみると、データが取得できました。

>> Article.release_planing
  Article Load (0.1ms)  SELECT "articles".* FROM "articles" WHERE "articles"."release" = ?  [["release", 0]]
=> [#<Article:0x0000000113a72880 id: 6, title: "りんご", release: "planing", payment: "pending", created_at: Thu, 16 Jun 2022 14:46:19.512793000 UTC +00:00, updated_at: Thu, 16 Jun 2022 14:46:31.508439000 UTC +00:00>, #<Article:0x0000000113a727b8 id: 7, title: "とうふ", release: "planing", payment: "done", created_at: Thu, 16 Jun 2022 14:48:02.847416000 UTC +00:00, updated_at: Thu, 16 Jun 2022 14:48:02.847416000 UTC +00:00>]

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/rails_7_0_minimal_app

 
今回のプルリクはこちらです。
https://github.com/thinkAmi-sandbox/rails_7_0_minimal_app/pull/2