ホワイトヘルスケア - テックブログ

ホワイトヘルスケアは、日本のヘルスケア領域における社会課題に正面から向き合い、現実世界を生きる人々の不安や痛みといった見過ごされるべきでない問題に対して本質的な解決に取り組むことで、持続的な医療システム・社会の実現を目指します。

ActiveRecordのenumにvalidateオプションが追加されてて嬉しい

こんにちは、エンジニアの菅野です。

今回はRailsの最近の変更で地味に嬉しかった enumのvalidateオプションについて書いていきます。

概要

Rails7.1からActiveRecordenumvalidate オプションが指定できるようになりました。

PR

github.com

これにより validate: true が指定された場合、 個別にvalidationを書かずとも対象外の値が入力された際にバリデーションエラーとして扱ってくれるようになりました 🎉

実践

例えばこんなテーブルがあったとします。

# migration
class CreateCountries < ActiveRecord::Migration[7.1]
    def change
        create_table :countries do |t|
            t.string :name, null: false
            t.string :code, limit: 2, null: false
            t.integer :continent, limit: 1, null: false
            t.datetime :created_at, null: false
        end
    end
end

モデルクラスは以下で、まだ validate オプションはつけていません。

class Country < ApplicationRecord
    enum :continent, {
        asia: 'Asia',
        africa: 'Africa',
        europe: 'Europe',
        north_america: 'NorthAmerica',
        south_america: 'SouthAmerica',
        australia_oceania: 'Australia/Oceania'
    }

    validates :name, presence: true
    validates :code, presence: true
end

まずはサンプルとして日本のレコードをつくります。

[1] pry(main)> japan = Country.create(name: 'Japan', code: 'JP', continent: :asia)
  TRANSACTION (0.4ms)  BEGIN
  Country Create (2.4ms)  INSERT INTO `countries` (`name`, `code`, `continent`, `created_at`) VALUES ('Japan', 'JP', 'Asia', '2023-11-02 02:22:45.486031')
  TRANSACTION (3.1ms)  COMMIT
=> #<Country:0x0000ffff954d74d0
 id: 1,
 name: "Japan",
 code: "JP",
 continent: "asia",
 created_at: Thu, 02 Nov 2023 02:22:45.486031000 UTC +00:00>
[2] pry(main)> japan.continent
=> "asia"

続いて continent に範囲外の値を指定します。 試しに日本を南極大陸に移動させてみましょう。

[3] pry(main)> japan.continent = :antarctica
ArgumentError: 'antarctica' is not a valid continent

値を代入した維持点で ArgumentError が発生してしまいました。

今度はモデルクラスのenum定義で validate オプションを有効にしてみます。

enum :continent, {
    asia: 'Asia',
    africa: 'Africa',
    europe: 'Europe',
    north_america: 'NorthAmerica',
    south_america: 'SouthAmerica',
    australia_oceania: 'Australia/Oceania'
}, validate: true

改めて範囲外の値を代入します。しかし今回ArgumentErrorは発生しません。

[2] pry(main)> japan = Country.last
  Country Load (3.0ms)  SELECT `countries`.* FROM `countries` ORDER BY `countries`.`id` DESC LIMIT 1
=> #<Country:0x0000ffff8e9ba788
 id: 1,
 name: "Japan",
 code: "JP",
 continent: "asia",
 created_at: Thu, 02 Nov 2023 02:22:45.486031000 UTC +00:00>
[3] pry(main)> japan.continent = :antarctica
=> :antarctica

続いてモデルのバリデーションをかけたところで、初めてバリデーションエラーが発生するようになりました。

[3] pry(main)> japan.continent = :antarctica
=> :antarctica
[4] pry(main)> japan.valid?
=> false
[5] pry(main)> japan.errors.messages
=> {:continent=>["is not included in the list"]}
[6] pry(main)> 

代入時点で無条件に例外がスローされることもなくなり、他フィールドと同じようにActiveRecordのエラーとして扱えるようになりましたので、地味に嬉しい変更ではないかと思います。

利用バージョン

ruby '3.2.2'
rails '7.1.1'

まとめ

これまで個別に validates :xxx, in: { inclusion: %w[bla bla bla] } などと記述していたenum値のバリデーションが、7.1からoption一発で宣言できるようになりました。嬉しいですね。