大ちゃんの駆け出し技術ブログ

RUNTEQ受講生のかわいいといわれるアウトプットブログ

【バリデーション】numericality

はじめに

こんにちは!大ちゃんの駆け出し技術ブログです。

本記事ではnumericalityについて説明します。

これは数値に対して使われるバリデーションです。例えば、整数しか入力できないとか、〇〇以上〇〇未満しか入力できない、といった制約をつけることができます。

特に難しいということはないので、早速やってみましょう。

また、今回は数値型のバリデーションがしっかりと反映されているかを確認するために、RSpecを使用しモデルスペックを作成します。

テキトーにカラムを追加

アプリケーション自体今回は関係ないのでテキトーなモデルに対し、数値型カラムを追加してくださいww

カラムの追加の方法は下記のとおりです。

 $ rails g migration Addカラム名Toテーブル名 カラム名:型

私は既存のUserモデルに対して、test_numericalityカラムをinteger型で追加します。

$ rails g migration AddTestNumericalityToUsers test_numericality:integer
      invoke  active_record
      create    db/migrate/20210217081751_add_test_numericality_to_users.rb

マイグレーションファイルを念のため確認。

class AddTestNumericalityToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :test_numericality, :integer
  end
end

問題なさそうですね。DBレベルに反映しましょう。

$ rails db:migrate
== 20210217081751 AddTestNumericalityToUsers: migrating =======================
-- add_column(:users, :test_numericality, :integer)
   -> 0.0024s
== 20210217081751 AddTestNumericalityToUsers: migrated (0.0026s) ==============

モデルスペックの作成

モデルスペックを作成します。Rspecはインストール済みであるという認識で進めていきます。

モデルスペックファイルの作成

$ rails generate rspec:model user
      create  spec/models/user_spec.rb
      invoke  factory_bot
      create    spec/factories/users.rb

FactoryBotにはtest_numericalityにとりあえず10を入れておきましょう。なお、nameカラムに関しては自分のUserモデルにあるカラムなので注意してくださいね!

# spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { 'test_name' }
    test_numericality { 10 }
  end
end

あと、FactoryBot作成時に省略記法を使用したいので、rails_helper.rbに下記内容を記載します。

# spec/rails_helper.rb
config.include FactoryBot::Syntax::Methods

モデルスペックファイルに下記内容を記載

# spec/models/user_spec.rb
RSpec.describe User, type: :model do
  describe "バリデーションのテスト" do
    it '全てのカラムが正常値の時、Userモデルはvalidである' do
      user = build(:user)
      expect(user).to be_valid
      expect(user.errors).to be_empty
    end
  end
end

これでテストを実行すると当然通ります。バリデーションがないためです。

$ bundle exec rspec spec/models/user_spec.rb
.

Finished in 0.02555 seconds (files took 1.75 seconds to load)
1 example, 0 failures

numericalityを試す

大変お待たせいたしました笑。

指定のモデルに対してnumericalityを使用してバリデーションをかけてみましょう。

numericalityは基本的に以下のように記述します。

validates :カラム名, numericality: { オプション: オプションに対応した値 }

では最初に簡単なものから紹介します。

only_integer

これは値を整数のみ入力できるようにするバリデーションです。

# app/models/user.rb
validates :test_numericality, numericality: { only_integer: true }

現状はFactoryBotにデフォルトで10が格納されているためテストは通るはずです。この値を小数点ありの10.5にしてみましょう。

RSpec.describe User, type: :model do
  describe "バリデーションのテスト" do
    it '全てのカラムが正常値の時、Userモデルはvalidである' do
      user = build(:user, test_numericality: 10.5)
      expect(user).to be_valid
      expect(user.errors).to be_empty
    end
  end
end

これでテストを実行すると、

$ bundle exec rspec spec/models/user_spec.rb
F

Failures:

  1) User バリデーションのテスト 全てのカラムが正常値の時、Userモデルはvalidである
     Failure/Error: expect(user).to be_valid
       expected #<User id: nil, name: "test_name", created_at: nil, updated_at: nil, test_numericality: 10> to be valid
             , but got errors: Test numericality must be an integer
     # ./spec/models/user_spec.rb:8:in `block (3 levels) in <top (required)>'

Finished in 0.03144 seconds (files took 1.03 seconds to load)
1 example, 1 failure

エラー内容の一部に、「Test numericality must be an integer」とあります。

user.errors[:test_numericality]
=> ["must be an integer"]

次のオプションに移る前に作成するFactoryBotを元の状態に戻しておいてください。

RSpec.describe User, type: :model do
  describe "バリデーションのテスト" do
    it '全てのカラムが正常値の時、Userモデルはvalidである' do
      user = build(:user)
      expect(user).to be_invalid
      # binding.irb
      expect(user.errors).to be_empty
    end
  end
end

greater_than、greater_than_or_equal_to

次は「〜より大きい」、「〜より以上」を表すバリデーションです。「〜より大きい」はgreater_thanです。さきほどのオプションであるonly_integerではboolean型であるtrueを入れましたが、今回は数値型を入れます。数値は10を入れてみましょう。

# app/models/user.rb
validates :test_numericality, numericality: { greater_than: 10 }

これでテストを実行すると失敗します。

$ bundle exec rspec spec/models/user_spec.rb
F

Failures:

  1) User バリデーションのテスト 全てのカラムが正常値の時、Userモデルはvalidである
     Failure/Error: expect(user).to be_valid
       expected #<User id: nil, name: "test_name", created_at: nil, updated_at: nil, test_numericality: 10> to be valid
             , but got errors: Test numericality must be greater than 10
     # ./spec/models/user_spec.rb:7:in `block (3 levels) in <top (required)>'

Finished in 0.02056 seconds (files took 0.95972 seconds to load)
1 example, 1 failure

エラー内部には以下の文字列がありますね。

"Test numericality must be greater than 10"

「10より大きい数」と制約しているので、10は含まれないということですね。10を含ませるためには「10以上の数」と制約をかけます。greater_than_or_equal_toを使用すると「〜以上の数」という制約をかけることができます。

# app/models/user.rb
validates :test_numericality, numericality: { greater_than_or_equal_to: 10 }

これでテストが通ります。

$ bundle exec rspec spec/models/user_spec.rb
.

Finished in 0.02247 seconds (files took 1.64 seconds to load)
1 example, 0 failures

「〜より大きい」、「〜より以上」の反対として「〜より小さい」、「〜より以下」もあります。less_thanless_than_or_equal_toです。この2つについては皆さんの手で試してみてください。

equal_to, other_than

指定した値以外が格納されるとエラーが出るようにしたい場合はequal_toです。

validates :test_numericality, numericality: { equal_to: 100 }

これでは現在のテストは当然通りません。

$ bundle exec rspec spec/models/user_spec.rb
F

Failures:

  1) User バリデーションのテスト 全てのカラムが正常値の時、Userモデルはvalidである
     Failure/Error: expect(user).to be_valid
       expected #<User id: nil, name: "test_name", created_at: nil, updated_at: nil, test_numericality: 10> to be valid, 
             but got errors: Test numericality must be equal to 100
     # ./spec/models/user_spec.rb:7:in `block (3 levels) in <top (required)>'

Finished in 0.05692 seconds (files took 1.78 seconds to load)
1 example, 1 failure

Test numericality must be equal to 100」とあるように値が必ず100でなければなりません。

反対に指定の数値だけ絶対に格納されてはいけないという制約もあります。other_thanを使います。

validates :test_numericality, numericality: { other_than: 100 }

テストが通りました。

$ bundle exec rspec spec/models/user_spec.rb
.

Finished in 0.02088 seconds (files took 1.72 seconds to load)
1 example, 0 failures

odd、even

oddは奇数、evenは偶数を制約します。only_integerと同様にboolean型で指定します。

oddtrueとしておけば、

validates :test_numericality, numericality: { odd: true }

テストは通りませんが、

$ bundle exec rspec spec/models/user_spec.rb
F

Failures:

  1) User バリデーションのテスト 全てのカラムが正常値の時、Userモデルはvalidである
     Failure/Error: expect(user).to be_valid
       expected #<User id: nil, name: "test_name", created_at: nil, updated_at: nil, test_numericality: 10> to be valid, but got errors: Test numericality must be odd
     # ./spec/models/user_spec.rb:7:in `block (3 levels) in <top (required)>'

Finished in 0.04629 seconds (files took 1.79 seconds to load)
1 example, 1 failure

evenをtrueにすると、

validates :test_numericality, numericality: { even: true }

テストはパスしました。

$ bundle exec rspec spec/models/user_spec.rb
.

Finished in 0.02802 seconds (files took 1.8 seconds to load)
1 example, 0 failure

終わりに

本記事では雑にカラムを追加しているので具体的な用途がわからない方もいるのかもしれませんが、数値の制約をすることで、入れたい値、入れたくない値を指定できるので安心できます。

格納されるべき値を簡単に制限できるので、使うタイミングがあればすぐに導入できるので本当に便利で使い勝手が良いと思います。

以上、大ちゃんの駆け出し技術ブログでした!