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

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

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

この項目については、データベースのリレーションについて知っていると、理解の助けになると思います。「社員は部署に、部署は会社に属する」などのように、「社員」「部署」「会社」といったモデル間の関連付けをする機能です。様々な関連付けの種類があるので、図にまとめてみました(図の中のコードは分かりやすいように簡略化しているので、実際は文法に即して記述します)。

基本的なbelongs_toとhas_many

複数の注文を1人の顧客が行っているような場合には、「注文」モデルと「顧客」モデルの両方面から「belongs_to(~に属する)」と「has_many(~を複数持つ)」をそれぞれ指定します。ここでのポイントは、belongs_toを使う際は、関連付けるためのID(下記の例では「顧客ID」)の属性を「注文」モデルに追加するマイグレーションが必要ということです。

基本的な関連付けbelongs_toとhas_many

図1:基本的な関連付けbelongs_toとhas_many(クリックで拡大)

「注文 belongs_to 顧客」に「counter_cache: true」を追加しておくと、ある顧客の合計注文数が増減したときにその顧客の「注文数」属性が自動で更新されるようになります。そうすると、顧客の総注文数を知りたいときに、わざわざ注文モデルのデータを取得しなくても済みます。couter_cacheを使った方がいいかどうかは、検索と更新どちらの頻度が高いかで決まります。図2の例では、顧客モデルの「注文数」属性はマイグレーションで追加することになります。

counter_cacheを追加することで、「注文数」は自動で更新される

図2:counter_cacheを追加することで、「注文数」は自動で更新される(クリックで拡大)

「注文 belongs_to 顧客」に「dependent: :destroy(または:delete)」を追加しておくと、ある顧客のデータがdestroyされたときに、関連する注文も削除されるようになります。

「注文 belongs_to 顧客」に「touch: true」を追加しておくと、ある顧客のデータが保存、またはdestroyされたときに、関連する注文のタイムスタンプが更新されるようになります。

別のモデルを仲介(through)するbelongs_toとhas_many

医者と患者の関係は、予約を介して結び付けられます。このようなときに「through」を使います。

別のモデルを介してモデル間を結び付けるthrough

図3:別のモデルを介してモデル間を結び付けるthrough(クリックで拡大)

ある医者が担当する患者を知るためには予約を仲介し、ある患者を担当する医者を知るためにも予約を仲介します(診察ごとに担当医が違うこともありますよね)。

予約モデルを介して医者モデルと患者モデルを結び付ける

図4:予約モデルを介して医者モデルと患者モデルを結び付ける(クリックで拡大)

「予約」モデルのように、「医者」と「患者」の関連付け以外の属性(予約日時など)がある場合に「through」を使います。

関連先のデータを1つしか持たないhas_one

販売者がそれぞれアカウントを1つだけ作成できる場合などには、「has_one」で関連付けます。関係が1:1ならば1つのモデルにまとめても構わないと思いますが、この方が使いやすいのかもしれませんし、将来的に「has_many」に変更しやすいのかもしれません。

「has_one」でも「has_many」と同じように「belongs_to」や「through」を設定できます。

1:1の結び付きを表すhas_one

図5:1:1の結び付きを表すhas_one(クリックで拡大)

関連付けるだけのhas_and_belongs_to_many

単に2つのモデルを関連付けるだけの場合は、「through」せずに「has_and_belongs_to_many」で関連付けます。「医者_病院」モデルには自身のIDは不要です。has_many_and_belongs_to_manyを使うときは、関連付けテーブルを作成するためのマイグレーションが必要です。

モデル間を関連付けるだけのhas_and_belongs_to_many

図6:モデル間を関連付けるだけのhas_and_belongs_to_many(クリックで拡大)

複数の関連付けを行うポリモーフィック

DRY(Don't Repeat Yourself:同じことを繰り返さない)の原則に基づいて、重複しているモデルを1つにまとめてしまう機能です。

図7の例では、医者も患者も自分の顔写真を登録できますが、「医者の顔写真」「患者の顔写真」の2つのモデルは構成がほぼ同じです。

ほぼ同じ構成の「医者の顔写真」「患者の顔写真」モデル

図7:ほぼ同じ構成の「医者の顔写真」「患者の顔写真」モデル(クリックで拡大)

これらを1つの「顔写真」というモデルにまとめ、ある顔写真が医者と患者どちらのモデルに属するかを、「対象のタイプ」(この例の場合、imageable_id)で指定します。「対象のID」(この例の場合、imageable_id)は医者IDか患者IDです。

polymorphicを用いて「顔写真」モデルを1つにまとめる

図8:polymorphicを用いて「顔写真」モデルを1つにまとめる(クリックで拡大)

自己結合のsubordinate

組織の階層のように、同じモデルのデータに関連付けるときに使うのが「subordinate」です。

同じモデルのデータに関連付ける際にはsubordinatesを用いる

図9:同じモデルのデータに関連付ける際にはsubordinatesを用いる(クリックで拡大)

最後に

いかがでしたか? バリデーション、コールバック、関連付けには他にも様々な機能があります。ガイドにはさらに詳しく書いてありますので、この記事を読んだ後にまたトライしてみてください。「こういう機能はすでにあったよなー」という程度の記憶でも、覚えておけば開発に役立つと思います。それではまた!

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

連載バックナンバー

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

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

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

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