はじめに
Rails 7.1.2 で 非カラム属性(non-column-backed attribute)に対して enum を再び使えるようになりました。ところが、この変更はブログ等であまり解説されておらず、理解に時間がかかったため、自分用の整理も兼ねて記事化します。
単なる使い方だけでなく、attribute/enum を どのクラス(モジュール)が提供しているのか まで掘り下げ、ActiveRecord と ActiveModel の役割もあわせて整理します。
背景・動機
実務コードで「非カラム属性に enum」のパターンを見かけ、意味が掴めず調査に時間がかかりました。実際のコードは出せないため、CHANGELOGのサンプルで説明します。
参照:Rails 7.1.2 CHANGELOG(enum を非カラム属性で再サポート。「明示型の attribute 宣言が必須」)
class Post < ApplicationRecord attribute :topic, :string enum topic: %i[science tech engineering math] end
このコードで一番の疑問は「attribute とは何者か?」でした。attribute を使うと DBカラムを持たない仮想属性にも型を与えられる ため、7.1.2 以降は その仮想属性に enum を定義できる、というのがポイントです。
実例・やってみたこと
まずは最小例です(posts テーブルに topic カラムは無い想定)。
class Post < ApplicationRecord attribute :topic, :string, default: "science" # 非カラム属性に明示型 enum :topic, %i[science tech engineering math], _prefix: :topic end
使い勝手(抜粋):
判定: post.topic_science?, post.topic_tech?
変更: post.topic_science!, post.topic = "tech"
列挙: Post.topics → { "science" => "science", ... }(文字列マッピング)
重要:これは仮想属性なので このままではDBに保存されません。確認例:
Post.attribute_types.keys.include?("topic") # => true (仮想属性も型表に載る) Post.column_names.include?("topic") # => false (DBカラムではない) post = Post.new post.topic # => "science"
注意点
既存のDBカラムに対しては attribute を追加する必要は基本ありません。 enum が生成するスコープ(例:Post.science)は、DBカラムが無いと検索に使えません。
学び・気付き・解釈
- 「attribute を先に明示」するのが前提
非カラム属性に enum を使う場合は、必ず attribute :name, :type を先に宣言する必要があります。これが 7.1.2 の再サポート条件です。
ActiveRecord と ActiveModel の役割(どこが何を提供している?)
attribute
土台は ActiveModel::Attributes(型付き属性の仕組み)。
ActiveRecord 側にも ActiveRecord::Attributes があり、ActiveModel の仕組みを ARモデルの世界(永続化・クエリ)へ統合しています。
参考:ActiveRecord 側の実装文脈
-
ActiveRecord::Enum の機能。ActiveModel(ActiveModel::Model など)には enum は無い点に注意。
ActiveRecord と ActiveModel の関係
継承関係ではありません。ActiveRecord::Base が ActiveModel の複数モジュール(Validations / Dirty / Attributes など)を取り込んでいます。
ActiveModel は「モデル機能の部品置き場」、ActiveRecord は「DB永続化を含む機能を提供するクラス」という感じでしょうか?
まとめ
Rails 7.1.2 で、非カラム属性 + 明示型 attribute に対して enum を再び使えるようになった。
attribute は ActiveModel が土台、enum は ActiveRecord の機能――この切り分けを押さえると理解がスッキリします。
あまり馴染みのないコードだったので、深堀りしてみました。
そのおかげで、Railsの内部処理についても知ることができとても良い機会でした!
また、見たことないなと思うコードがあったときは、深堀りしてみようと思います。