Active Recordのバリデーションやコールバックについて深く掘り下げてみた

2015年3月18日(水)
野田 貴子

はじめに

このコラムは、Ruby初心者の著書が、Railsガイドに沿って、リアルタイムで勉強をしていくコラムです。全12回を予定しています。勉強する上でつまずいた点やその回避法、他のプログラミング言語や職業経験に基づいたアドバイスなども紹介する予定です。RubyやRailsに興味のある方は、ぜひ一緒に勉強してみませんか。

今回参照するガイドはこちらです。

Active Record バリデーション

http://railsguides.jp/active_record_validations.html

Active Record コールバック

http://railsguides.jp/active_record_callbacks.html

Active Recordの関連付け(アソシエーション)

http://railsguides.jp/association_basics.html

Active Recordバリデーション

バリデーション(validation)という単語を聞きなれない方もいるかもしれません。あるものがvalid([形容詞]有効な)であるかどうかを調べることをvalidate([動詞]有効にする、有効だと確認する)といいます。Webアプリケーションでは様々な場所からデータを受け取るので、それらが適切であるかどうかを調べる仕組みをvalidation([名詞]検証)といいます。

Railsでは、「ユーザー名が何億文字もの長さになっていないか」、「電話番号に数字以外の文字が混ざっていないか」のようなテキストを調べる単純な検証から、「予約したい会議室の予約時間が、他の人とかぶっていないか」のようなデータベースのデータを参照する検証まで、同じように設定することができます。

Webアプリケーションにおいて、外部からの入力値を検証する機会は何度もありますが、この中でActive Recordバリデーションがどのタイミングに当たるのでしょうか。以下の表で確認してみましょう。

Webアプリケーションによる入力値検証のタイミング

タイミング実現方法
1.ユーザーがデータを入力する前
(検証ではなく予防に当たりますが…)
・入力欄に文字数制限をつけ、10文字までしか入力できないようにする
・入力欄のキーボードを数値入力に固定する
HTML
2.ユーザーがデータを入力している間・入力欄の内容に1文字でも変更があったタイミングで入力欄のデータを検証し、間違いがあればユーザーに知らせるJavaScript
3.ユーザーがデータを入力した後・入力欄からカーソルが外れたタイミングで入力欄のデータを検証し、間違いがあればユーザーに知らせるJavaScript
4.ユーザーがデータを送信する前・「送信」ボタンを押したタイミングで全入力欄のデータを検証し、間違いがあればユーザーに知らせるJavaScript
5.プログラムがデータを受け取った後・コントローラの処理の中で、取得したデータを検証する
・モデルに渡されたデータが、決められたルールに合うかどうかを検証する ← Active Recordのバリデーション機能
Rails
6.データベースに更新がかかった時・データベースに渡されたデータが、決められたルールに合うかどうかを検証するデータベース

上記の1はHTMLで、2〜4はJavaScriptで制御します。ここまでがクライアント(ブラウザ)側での処理です。一方5以降は、サーバーにデータが送信された後の、サーバー側の処理です。基本的には、できるだけ早い段階でユーザーに入力内容の間違いを伝えるのが良いといえます。

ただし、ブラウザでJavaScriptがOFFになっていることもありますし、通信の途中でデータが改ざんされる可能性もあるので、クライアント側での検証に加えて、プログラム側での検証も必須です。またコントローラ側でもデータの検証はできますが、コントローラはアルゴリズムを書くことに専念し、受け取ったデータはそのままモデルに渡した方がシンプルになります。Active Recordのバリデーション機能では、コントローラなどからモデルに更新(create、update、saveなど)があるたびに、必ず同じ検証を通すことができるので、検証漏れもなくせます。

とはいえ、Railsではコントローラからモデルを更新しても、メソッドによってはバリデーションをスルーするものがあります。これを見落とすとバグになってしまうので、使い分けが重要ですね。自動でバリデーションをしないものは、valid?やinvalid?メソッドを使ってバリデーションさせられます。

バリデーションをするもの

  • create
  • create!
  • save
  • save!
  • update
  • update!

バリデーションをしないもの

  • decrement!
  • decrement_counter
  • increment!
  • increment_counter
  • toggle!
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters
  • save(validate: false)

それぞれのメソッドの使い方は、ドキュメントで確認できます。

Railsガイドを見ると、Active Recordのバリデーションは、種類が豊富なことが分かります。数量や長さなどの数値を検証する場合は、以下の2点に注意が必要だと感じました。

  1. 対象の数値を含むのか、含まないのか、
  2. エラーメッセージの単数形・複数形の考慮(多言語化する場合)

1に関する例ですが、以下のようなバリデーションは、すべて対象の数値を超えた場合にエラーになるようです。

class Person < ActiveRecord::Base
  validates :name, length: { minimum: 2 }     # 2以上はOK、1はNG
  validates :bio, length: { maximum: 500 }     # 500以下はOK、501はNG
  validates :password, length: { in: 6..20 }     # 6~20はOK、5や21はNG
  validates :registration_number, length: { is: 6 }     # 6はOK、5や7はNG
end

2については、英語などの単数・複数で変化がある言語では、1の場合とそれ以外でエラーメッセージの書き方を変える必要が出てきます。デフォルトのエラーメッセージは複数形で書かれているので、次のようなバリデーションでエラーになると、

class Person < ActiveRecord::Base
  validates :name, length: {minimum: 1}
end

「is too short (minimum is 1 characters)」と出力されてしまいます。その場合は以下のようにエラーメッセージを指定するか、

class Person < ActiveRecord::Base
  validates :name, length: {
minimum: 1,
too_short: “is too short (minimum is %{count} character)”
}
end

以下のように、lengthバリデータよりも先にpresenceバリデータにひっかかるようにします。

class Person < ActiveRecord::Base
  validates :name, presence: true , length: {minimum: 1}
end

presenceでエラーになると「can't be blank」と表示されます。

このような単純な検証以外にも、「名前が“x”で始まっているかどうか」「対象の会議室の予約時間がかぶっていないか」のような自作のバリデーションを作成する方法が書かれているので、便利に使えそうです。

Active Record コールバック

コールバックとは、ある処理の前後に実行されるように予約しておく処理のことです。例えば、データベースにデータを保存する直前に実行するコールバックとして、「入力されたメールアドレスを小文字化する」、「入力された住所の数字を半角に直す」、「部署情報で入力された親組織ID(例:C001)を元に、自部署までの構造上のパス(例:A001-B020-C001)を作成する」といった処理などが挙げられます。モデルにコールバックを設定しておくことで、コントロール側の処理をシンプルに保てます。

コールバックは実行されるタイミングが重要です。オブジェクトのCRUD(生成、読み込み、更新、削除)それぞれについて、以下のようなタイミングでコールバックが利用できます。

オブジェクトの作成時

  1. before_validation
  2. after_validation
  3. before_save
  4. around_save
  5. before_create
  6. around_create
  7. after_create
  8. after_save

オブジェクトの更新時

  1. before_validation
  2. after_validation
  3. before_save
  4. around_save
  5. before_update
  6. around_update
  7. after_update
  8. after_save

オブジェクトの削除時

  1. before_destroy
  2. around_destroy
  3. after_destroy

オブジェクトの読み込み時

  1. after_find
  2. after_initialize

たくさんありすぎて分からなくなりそうですので、各メソッドにおいて、バリデーションやコールバックが呼ばれるかどうかを表にまとめました。

メソッドごとのバリデーション、コールバック呼ぶかどうかの違い

バリデーションコールバック
create
create!
save
save!
save(validate: false)×
update
update!
update_all××
update_attribute×
update_column××
update_columns××
update_counters××
decrement×
decrement!×
decrement_counter××
increment×
increment!×
increment_counter××
toggle×
toggle!×
touch××
destroy
delete×
delete_all×
destroy!
destroy_all
valid?
all
first
find, find_by, find_by_*, find_by_*!, find_by_sql
last

1983年生まれ。大学卒業後、ソフトウェア開発の営業を経て、ソフトウェア開発業務に転向。現在は自社パッケージのフロントエンド開発のほか、PHPでの受託開発案件、日→英のローカライズ案件などを担当。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています