LoginSignup
23
19

More than 3 years have passed since last update.

Railsでboolean型のカラムのvalidationを行い型が異なる場合エラーとして返す方法

Last updated at Posted at 2017-07-03

初めに

  • ruby 2.3.1
  • Rails 5.0.2
  • custom validatortype castの話が出てきますが、この投稿では深くは扱っていません

問題

  • Web APIを開発していて、boolean型のカラムのvalidateを行いたく、ググってよくヒットする書き方を試した
  • boolean型のアトリビュートに対して型違いの更新リクエストを行いエラーを期待したが、なぜか成功扱い、にもかかわらず値は変わらずで矛盾していた
  • model内でself.アトリビュート名を使いリクエストした値を取得しようとしたが、なぜかリクエスト以前の値が取得できた(=リクエストが正常に行われていないように見えた)

migrate

db/migrate/20170703183050_create_my_models.rb
class CreateMyModels < ActiveRecord::Migration[5.0]
  def change
    create_table :my_models do |t|

      t.boolean :my_attribute, default: false, null: false

    end
  end
end

model

app/model/my_model.rb
class MyModel < ApplicationRecord

  validates :my_attribute, inclusion: { in: [true, false]}

end

リクエストとレスポンス

PATCHリクエストボディ
{ 
    "my_attribute": 1
}
PATCHレスポンスボディ
{
    "my_attribute": false,
    "message": "更新が正常に完了しました。"
}

原因

  • modelにリクエストが到達するとtype castが行われる
  • 型違いの場合、リクエストされた値は破棄されエラーも出ない
  • リクエストされた値が破棄された後のself.アトリビュート名の中にはリクエスト以前の値(=正常な値)が入っていた為、validatesに合格していたっぽい 注釈1

解決策

  • before_type_castを使う
  • アトリビュート名_before_type_castと書くことでメソッドが呼び出せ、type castされる前の値を取得することが出来る
  • ただ、これだけだと後述の余談の部分に書くようなmessageになってしまうためcustom validatorと組み合わせた

model

app/model/my_model.rb
class MyModel < ApplicationRecord

  validates :my_attribute, boolean_before_type_cast: true

end
  • validates :アトリビュート名, バリデーター名: trueの形で呼ぶ

validator

app/validator/boolean_before_type_cast_validator.rb

class BooleanBeforeTypeCastValidator < ActiveModel::EachValidator

  def validate_each(record, attr, value)
    value = record.send("#{attr}_before_type_cast")
    if !value.is_a?(FalseClass) && !value.is_a?(TrueClass) && !(value == 'false') && !(value == 'true')
      record.errors.add(:base, "#{attr.to_s.humanize}に設定出来ない値が指定されています")
    end
  end

end
  • ファイル名とクラス名の関係性はrailsの規約に適合させる
  • 引数のrecordにMyModelオブジェクトが入っている
  • 引数のattrにmodelのvalidatesで指定したmy_attributeアトリビュートが入っている
  • 引数のvaluetype cast後の値が入っている為使わない
  • migrateにてdefault: falseを指定している為、my_attributeの値指定が無い場合(例えばcreateリクエストの場合)は、string型の"false"が自動的に補完されてリクエストされ、dbにはboolean型で保存される
  • my_attributeにboolean型の値が指定されたリクエストの場合でも、railsの仕様上string型の"true"または"false"がリクエストされ、dbにはboolean型で保存される。rails consoleで行ってもweb API経由で行っても同じ結果になる
  • rspecとfactorygirlを使っている場合は注意が必要(後述)

リクエストとレスポンス

PATCHリクエストボディ
{ 
    "my_attribute": 1
}
PATCHレスポンスボディ
{
    "message": "更新に失敗しました。My attributeに設定出来ない値が指定されています"
}

rspecとfactorygirlを使っている場合の注意

  • factorygirlのfactoryメソッドにてmy_attributeの値をboolean型で記述しているとboolean型でリクエストが行われる
  • テストを成功させる為にはvalidatorの条件に!value.is_a?(FalseClass) && !value.is_a?(TrueClass)を入れるか、factoryにてstring型で値を指定する
  • factorygirl以外でboolean型でリクエストが行われるケースがあるか不明な為、validatorの条件に!value.is_a?(FalseClass) && !value.is_a?(TrueClass)を追加しておく方が安全且つfactoryの記述時に型を意識しなくても済む

余談 - custom validatorを使わないとこんな感じのmessage

modelをこう書く

app/model/my_model.rb
class MyModel < ApplicationRecord

  validates :my_attribute_before_type_cast, inclusion: { in: [true, false]}

end
PATCHリクエストボディ
{ 
    "my_attribute": 1
}
PATCHレスポンスボディ
{
    "message": "更新に失敗しました。My attribute before type castに設定出来ない値が指定されています"
}
  • validates :アトリビュート名_before_type_cast, inclusion: { in: [true, false] }の形で呼ぶ

  • アトリビュート名に_before_type_castがくっついてしまう

参考にしたwebページ

武内修@筑波大 - rails/validatesでbefore_type_cast

  • 上記のページが大変参考になりました
  • というかrails before_type_castでググっても1500件くらいしかヒットしない、ヤバい

その他

  • boolean型を検証してちゃんとエラーで返す方法はググっても殆ど情報が無かった。他の人はどうやっているんだろうか
  • もっと別のやり方、綺麗な書き方、間違っている点など、ご指摘あれば是非お願い致します
  • factorygirlにはハマりました。

追記

rails 6.0.3.2を使ってかなりプレーンに近い条件で再度試したところ、相変わらず型違いが検証されず通ってしまう結果となりました。
これは環境によるんでしょうか。なぞいです

バリデーション(is_adminカラムに注目ください)

スクリーンショット 2020-08-02 19.33.49.png

リクエストとレスポンス

スクリーンショット 2020-08-02 19.30.10.png


  1. ここはよくわかっていないです。実際にはmigrateでboolean: trueと書いた部分でvalidateが行われているだけで、validates :my_attribute, inclusion: { in: [true, false]}は無意味な記述になっていたんじゃないかと推測しています 

23
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
19