Railsのモデルで「(いわゆる)マスタを作るほどでもないけど数値に名前をつけたい」という時に、 ActiveRecord::Enum
(enum) が使えます。
ActiveRecord::Enum
enum については、以下をはじめ、詳しく解説している記事がすでに存在します。
Railsのenumを使いこなす方法(翻訳)|TechRacho by BPS株式会社
ただ、自分で動かしてみないと分からないことがあるため、動かしてみた時のメモを残します。
なお、今回はRails7から enum に追加された新しい構文で書いてみます。
Rails 7のenumに新しい構文が導入(翻訳)|TechRacho by BPS株式会社
目次
環境
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も自動生成されます。
例えば、 payment
が pending
のもののみを取得するような 処理をしてみます。
まずはデータの準備です。 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を定義する時のオプションとして、 prefix
や suffix
があります。
ここでは prefix
オプション付きで定義してみます。
class Article < ApplicationRecord enum :payment, { planing: 0, done: 1, pending: 2 } enum :release, { planing: 0, done: 1, pending: 2 }, prefix: true # 追加 end
prefixオプション付きで定義したことでエラーにならず、また、 pending
と release_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