Active Recordのバリデーションやコールバックについて深く掘り下げてみた
はじめに
このコラムは、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バリデーションがどのタイミングに当たるのでしょうか。以下の表で確認してみましょう。
タイミング | 例 | 実現方法 |
---|---|---|
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の場合とそれ以外でエラーメッセージの書き方を変える必要が出てきます。デフォルトのエラーメッセージは複数形で書かれているので、次のようなバリデーションでエラーになると、
「is too short (minimum is 1 characters)」と出力されてしまいます。その場合は以下のようにエラーメッセージを指定するか、
以下のように、lengthバリデータよりも先にpresenceバリデータにひっかかるようにします。
presenceでエラーになると「can't be blank」と表示されます。
このような単純な検証以外にも、「名前が“x”で始まっているかどうか」「対象の会議室の予約時間がかぶっていないか」のような自作のバリデーションを作成する方法が書かれているので、便利に使えそうです。
Active Record コールバック
コールバックとは、ある処理の前後に実行されるように予約しておく処理のことです。例えば、データベースにデータを保存する直前に実行するコールバックとして、「入力されたメールアドレスを小文字化する」、「入力された住所の数字を半角に直す」、「部署情報で入力された親組織ID(例:C001)を元に、自部署までの構造上のパス(例:A001-B020-C001)を作成する」といった処理などが挙げられます。モデルにコールバックを設定しておくことで、コントロール側の処理をシンプルに保てます。
コールバックは実行されるタイミングが重要です。オブジェクトのCRUD(生成、読み込み、更新、削除)それぞれについて、以下のようなタイミングでコールバックが利用できます。
オブジェクトの作成時
- before_validation
- after_validation
- before_save
- around_save
- before_create
- around_create
- after_create
- after_save
オブジェクトの更新時
- before_validation
- after_validation
- before_save
- around_save
- before_update
- around_update
- after_update
- after_save
オブジェクトの削除時
- before_destroy
- around_destroy
- after_destroy
オブジェクトの読み込み時
- after_find
- 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 | − | ○ |
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Active Recordの使い方
- Railsでデータベースを扱うためのライブラリActive Recordについて学んでみた
- Oracle Cloud Hangout Cafe Season7 #2「IaC のベストプラクティス」(2023年7月5日開催)
- Installing OpenSolaris
- 多機能なHibernate(後編)
- Active Recordのその先へ ~RailsでMongoDBを使う~
- Iacツール「Terraform」の基本的な使い方
- Let's discover OpenSolaris!
- HTTPセッションの永続性確保
- ZFS and DTrace make management easy