PR

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のWebサイトにログインすることでさまざまな限定特典を入手できるようになります。

Think IT会員サービスの概要とメリットをチェック

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