yucatio@システムエンジニア

趣味で作ったものいろいろ

Validationがごちゃごちゃしてきたら、分類して整理する : 分類編

アプリを作成・改修していくうちに、モデルの属性が多くなり、validationも複雑になってきます。既存の項目と同時に設定できない項目が追加されたり、また、システムが大きくなってくると、外部のAPIと通信が必要といったケースもあります。

新しく追加した項目のvalidationをどんどん後ろに追加していくと、統一性がなく、新しく入ってきたメンバーにとって読みにくいコードになってしまいます。

そこで、validationを分類して整理することによって、コードを見やすく保ちます。

このページでは、どのような分類軸があるのかをみていきます。

なお、ユーザが情報を登録・編集・削除できて、一覧・詳細で入力内容を確認できるようなアプリを想定して話を進めます。

Validationの軸

単属性/属性間

単属性: モデルの属性うち、1属性だけを対象にvalidationする場合

属性間: モデルの属性のうち、2つ以上の属性を対象としてvalidationする場合

単属性のvalidationは、多くのwebフレームワークであらかじめ便利な方法(ヘルパーやアノテーション、設定ファイル)が用意されています。

属性間のvalidationはほとんどのwebアプリケーションでは便利な方法が用意されていません。そのため、多くの場合独自でコードを書く必要があります。

  • 単属性のvalidationの例

    • 氏名は1文字以上、32文字以下
    • 郵便番号は半角数字、7桁
    • パスワードに使える文字は半角英数字と:~@"'%+-*/;#&<>^=[]{}、8文字以上32文字以下
  • 属性間のvalidationの例

    • 開始時間は終了時間より前でなければいけない
    • 自宅電話番号または携帯電話番号は必須
    • ユーザの年齢が18歳未満の場合、保護者の氏名が必要

子属性の個数

1つの属性に対してリストを登録する場合に、リストの長さを制限します。

    • フリマアプリで、1つの商品に対して写真は4枚まで
    • アンケート作成アプリで、1つの質問に対して選択肢は5個まで

マージ値/DB値/入力値のうち、どれを使うか

マージ値: これからDBに保存しようとしている値

DB値: 現在DBに登録されている値

入力値: ユーザが入力した値(サーバに送信されてきた値)

f:id:yucatio:20220207112420p:plain

マージ値、DB値、入力値のうちどの組み合わせを使うかで全部で7パターンありますが、多いのは、マージ値のみか、マージ値とDB値を使う場合です。

  • マージ値のみ必要な例

    • イベント名は2文字以上32文字以下
    • 価格は50円以上、50万円以下である必要がある
    • ユーザが法人の場合には、会社名が必要
  • DB値とマージ値が必要な例

    • タスク管理アプリにて、タスクのステータスを着手中から未着手には変更できない
    • タスク管理アプリにて、タスクのステータスが完了の場合には、担当者を変更できない
    • イベント登録アプリで、イベント開始時間を過ぎている場合は、イベントの開始時間を変更できない
  • DB値とマージ値と入力値が必要な例

    • タスクのステータスを着手中から保留に変更するには、入力値に変更理由が必要

DBの値は、値が変更されているかや状態遷移が妥当かチェックする場合に使用されます。 Railsの場合は、#{attr_name}_in_databaseでDB値の取得、#{attr_name}_before_type_castで型変換前の値を取得することができます。

yucatio.hatenablog.com

マスタ情報に値が存在するかのチェックが必要/不要、さらにマスタ情報の値を用いてvalidationが必要/不要

ユーザがIDを入力して、そのIDがマスタテーブルに存在するかのチェックです。さらにそのIDに紐づいている情報を元にvalidationが必要なケースがあります。 マスタ情報はDBだったりAPIだったりします。マスタ情報の更新頻度が低い場合はキャッシュの導入など検討します。

  • マスタ情報に値が存在するかチェックが必要な例

    • 郵便番号は実在するものとする。郵便番号が正しいかどうかは外部のAPIを利用し、レスポンスコードで判別する
    • 配送方法はIDで指定する。配送方法マスタテーブルにそのIDが存在すること
  • マスタ情報に値が存在するかチェックが必要かつ、マスタ情報の値を用いてvalidationが必要な例

    • フリマアプリにて、入力された商品カテゴリIDがカテゴリマスタに存在する必要がある。カテゴリが存在するかはAPI問い合わせするものとし、レスポンスに{size: "required"}が含まれる場合は、サイズの入力が必要
    • クーポンコードは存在するものであること。クーポンが存在するかはAPIに問い合わせるものとし、レスポンスに対象の商品IDが記載されていること

Validationするために、DBの同じテーブルの別レコードの情報が必要/不要

uniqなどは、登録しようとするレコードだけでなく、登録しようとするレコードと同じテーブルにあるデータが必要になります。そのため、DBへの問い合わせが必要になります。 期間の重複チェックなどはDBへ適切なindexをはる必要があります。

  • 必要となる例
    • 登録に使用するメールアドレスは一意でなければいけない
    • 会議室予約アプリにて、1つの会議室で、会議の予約時間が重複してはいけない
    • フリマアプリで、1ユーザが出品できる件数はひと月あたり100件まで

DBへの問い合わせを必要な時にだけするにはこちらを参考にしてください。

yucatio.hatenablog.com

Validationするために、現在時刻が必要/不要

現在時刻の要不要でvalidationの実装の仕方や実装の難易度はあまり変わらないのですが、テストデータを用意する際に注意が必要となります。

  • 必要となる例
    • 会議室予約アプリにて、開始時間は現在時刻より後でなければいけない
    • 生年月日として入力できる値は、現在から100年前から、18年前まで

具体例

ここではどのvalidationがどのように分類されるか具体例でみていきます。

validation 単属性/属性間 マージ値/DB値/入力値 マスタ情報 DB他レコード 現在時刻
氏名は1文字以上、32文字以下 単属性 マージ値 不要 不要 不要
開始時間は終了時間より前でなければいけない 属性間 マージ値 不要 不要 不要
タスクのステータスが完了の場合には、担当者を変更できない 属性間 マージ値、DB値 不要 不要 不要
郵便番号は実在するものとする。郵便番号が正しいかどうかは外部のAPIを利用し、レスポンスコードで判別する 単属性 マージ値 必要 不要 不要
会議室予約アプリにて、1つの会議室で、会議の予約時間が重複してはいけない 属性間 マージ値 不要 必要 不要
生年月日として入力できる値は、現在から100年前から、18年前まで 単属性 マージ値 不要 不要 必要
フリマアプリで、1ユーザが出品できる件数はひと月あたり100件まで - マージ値 不要 必要 必要

続く

整理編に続く

yucatio.hatenablog.com

おわりに

少し前に携わったプロジェクトで、多くのvalidationを既存のアプリに追加しました。既存のアプリはvalidationが順不同に書かれており、バグも残っていました。既存の仕様と新たに追加する仕様を整理する中で、validationの分類をするとコードが書きやすくなることに気づきました。それをまとめたのがこのページです。

ここに挙げたvalidationの分類は、ブログ主が見たことあるvalidationからとったものなので、ほかにもっと分類の軸があるかも知れません。開発するシステムに合わせてアレンジしてみてください。