leaning diary Rails

【Learning Diary29】unlessを使ったバリデーションのカスタマイズ

【Learning Diary29】unlessを使ったバリデーションのカスタマイズ

 

今日はバリデーションをカスタマイズできることを知りました。

 

正直、指摘いただいた後に、何故気づかなかったのか不思議でした。

 

欲しいメソッド・機能はすでに先人が作ってくれていることがほとんどです。

 

Railsガイドを先に見る癖が最近薄れていたなと反省しました。

 

Active Record バリデーション

 

バリデーションの簡単なカスタマイズを試してみます。

 

まずは、モデルを用意。

 

❯ rails generate model Person
      invoke  active_record
      create    db/migrate/20231107110115_create_people.rb
      create    app/models/person.rb
      invoke    test_unit
      create      test/models/person_test.rb
      create      test/fixtures/people.yml

 

マイグレーションファイルに「名前」を設定。

 

class CreatePeople < ActiveRecord::Migration[7.0]
  def change
    create_table :people do |t|
      t.string :name

      t.timestamps
    end
  end
end

 

マイグレーションを実行。

 

❯ rails db:migrate
== 20231107110115 CreatePeople: migrating =====================================
-- create_table(:people)
   -> 0.0014s
== 20231107110115 CreatePeople: migrated (0.0014s) ============================

 

モデルには、名前に空文字が登録できないようバリデーション(presence:true) を設定します。

 

class Person < ApplicationRecord
  validates :name, presence: true
end

 

コンソールでレコードを作成し、バリデーションの有効性を確かめます。

 

valid? メソッドを使うとバリデーションがトリガされます。

 

オブジェクトにエラーがない場合はtrueを返し、エラーの場合はfalseを返します。

 

❯ rails c
Loading development environment (Rails 7.0.4.2)
>> Person
=> Person (call 'Person.connection' to establish a connection)
>> Person.create(name: "John Doe").valid?
  TRANSACTION (0.0ms)  begin transaction
  Person Create (0.7ms)  INSERT INTO "people" ("name", "created_at", "updated_at") VALUES (?, ?, ?)  [["name", "John Doe"], ["created_at", "2023-11-07 11:09:17.513911"], ["updated_at", "2023-11-07 11:09:17.513911"]]
  TRANSACTION (0.6ms)  commit transaction
=> true
>> Person.create(name: nil).valid?
=> false

 

上記から、確かに空文字の場合のバリデーションが有効であることがわかります。

 

続いて年齢を追加してみます。

 

❯ rails generate migration AddAgeToPerson age:integer
      invoke  active_record
      create    db/migrate/20231107111135_add_age_to_person.rb

❯ rails db:migrate
== 20231107111135 AddAgeToPerson: migrating ===================================
-- add_column(:people, :age, :integer)
   -> 0.0139s
== 20231107111135 AddAgeToPerson: migrated (0.0140s) ==========================

 

年齢が10歳以上である場合に限り登録できるように、モデルにバリデーションを追加します。

 

class Person < ApplicationRecord
  validates :name, presence: true
  validate :age_must_be_at_least_10

  private

  def age_must_be_at_least_10
    if age.present? && age < 10
      errors.add(:age, "は10歳以上である必要があります")
    end
  end
end

 

コンソールで追加したバリデーションの有効性を確かめます。

 

❯ rails c
Loading development environment (Rails 7.0.4.2)
>> Person.create(name: "John Doe", age:12).valid?
  TRANSACTION (0.0ms)  begin transaction
  Person Create (1.4ms)  INSERT INTO "people" ("name", "created_at", "updated_at", "age") VALUES (?, ?, ?, ?)  [["name", "John Doe"], ["created_at", "2023-11-07 11:25:53.861562"], ["updated_at", "2023-11-07 11:25:53.861562"], ["age", 12]]
  TRANSACTION (0.6ms)  commit transaction
=> true
>> Person.create(name: "John Doe", age:9).valid?
=> false
>>

 

12歳ではtrue、10歳ではfalseが返却されました。

 

意図したとおりできています。

 

ここで、空文字を登録できないnameのバリデーションの方は有効な状態で、名前に【.exc】が含まれていたら10歳未満でも登録できるように変更してみます。

 

バリデーションのメソッドの後に、unlessで条件を指定します。

 

class Person < ApplicationRecord
  validates :name, presence: true
  validate :age_must_be_at_least_10, unless: -> { name.include?(".exc") }

  private

  def age_must_be_at_least_10
    if age.present? && age < 10
      errors.add(:age, "は10歳以上である必要があります")
    end
  end
end

 

コンソールで挙動を確かめます。

 

>> Person.create(name: "John Doe", age:9).valid?
=> false


>> Person.create(name: "John Doe", age:10).valid?
  TRANSACTION (0.2ms)  begin transaction
  Person Create (1.2ms)  INSERT INTO "people" ("name", "created_at", "updated_at", "age") VALUES (?, ?, ?, ?)  [["name", "John Doe"], ["created_at", "2023-11-07 11:34:40.529798"], ["updated_at", "2023-11-07 11:34:40.529798"], ["age", 10]]
  TRANSACTION (1.5ms)  commit transaction
=> true


>> Person.create(name: "John Doe.exc", age:9).valid?
  TRANSACTION (0.2ms)  begin transaction
  Person Create (1.2ms)  INSERT INTO "people" ("name", "created_at", "updated_at", "age") VALUES (?, ?, ?, ?)  [["name", "John Doe.exc"], ["created_at", "2023-11-07 11:34:53.336576"], ["updated_at", "2023-11-07 11:34:53.336576"], ["age", 9]]
  TRANSACTION (2.3ms)  commit transaction
=> true

 

名前を「John Doe.exc」とすると、ageが9であってもエラーは生じず登録できる状態となることがわかりました。

 

シンプルな条件だったからでもありますが、思いの外短いコードでカスタマイズできました。

-leaning diary, Rails