【Rails初心者必見】has_manyでデータ管理を行おう!
はじめに
Railsアプリケーションでは、「has_many」や「belongs_to」などをモデルクラスに追記することで、モデル(テーブル)を関連付けられます。
モデル設計は、Railsアプリケーションの中心的な構造の1つですので、むしろ関連付けを使わない方が少ないかもしれません。
では、「has_many」や「belongs_to」の構文や具体的な動きを知っているでしょうか? また、モデルの関連付けを行う利点を理解しているでしょうか?
今回は、そんな関連付けについて、「has_many」を中心にして、説明していきます。
has_manyで関連付ける
モデルの「関連付け」というのは、あるモデルAのレコードを別のモデルBが参照している状態を指します。
このとき、モデルBにはモデルAのidを保持するカラムがあり、データを必要とするときにそのidからモデルAのレコードを特定してデータを取得します。この、取得するデータの内容はモデルAに依存しており、モデルBは関与できません。そのため、「モデルBがモデルAに従属している」と表現します。また、モデルAを親モデル、モデルBを子モデルと呼びます。
「has_many」は、この関連付けの中でも参照される側、つまり親モデルに設定する関連付けで、「一対多」の関連を表しています。
一対多
「一対多」というのは、親モデルのレコードが、子モデルの0以上のレコードから参照される関連付けです。
(図1)
図のように、あるユーザーには0以上の注文が発生しますが、1つの注文には1人のユーザーしかありません。これが、「一対多」の関連です。
多対多
「一対多」に対して「多対多」という関連付けもあります。
先ほどの図では、注文内容が重複しているものがありました。この注文内容の重複をなくした場合、注文内容から見ても、1つの注文内容に対して複数のユーザーが発生することになります。
このような場合が「多対多」の関連付けです。
「多対多」については、以下のようにモデル3つで表現します。(以下は、userとitemの「多対多」です。間にあるモデルを結合モデルと呼びます)
(図2)
関連付けのメリット
関連付けを行うことで、モデルをシンプルなものにし、重複したデータを持たないようにすることができます。(正規化と呼びます)
正規化されたデータベースは、シンプルで可読性が高く、メンテナンス性に優れています。
そのため、データベースを扱う上で、関連付けは必須と考えて良いものです。
has_manyのメリット
Railsでは、ここまでに説明した関連付けを実現するために、「has_many」などの関連を準備してくれています。
それら「has_many」などで関連付けを明示することで、内部的にいろいろなプログラムコードが自動生成され、処理をシンプルにすることができるのです。
例えば、先ほど「一対多」の項で紹介した図1で、ユーザーを削除するときのプログラムを見てみましょう。
「has_many」を使わない場合の削除処理は、次のようなプログラムになります。
@orders = Order.where(user_id: @user.id)
@orders.each do |order|
order.destroy
end
@user.destroy
※@userには、削除するユーザーのモデルオブジェクトが格納されています
「ユーザーを削除する」ということは、ユーザーに関連付けされている注文も削除しなければいけません。そのため、eachによるループが必要になっています。
これが、「has_many」と「belongs_to」などで関連付けを明示すると、次のようになります。
@user.destroy
先ほどのプログラムコードにあった、「orderモデルの関連する注文を削除する処理」がなくなっていますが、その部分はRailsが自動で処理を行ってくれるため、記述する必要がありません。
そのため、プログラムコードに「ユーザーを削除する」という動作そのものになり、非常にシンプルで分かりやすくなっています。
この記事を読んでいるあなたが、本気でプログラミング学習をするならDMM WEBCAMPのSKILLSコースがおすすめです!
ぜひご確認ください!
関連付けの種類
関連付けを明示するには、対象のモデルとどのような関連付けなのかを指定する必要があります。
Railsで指定できる関連付けは、以下のとおりです。
関連付け | 説明 |
---|---|
belongs_to | あるモデルに従属している関連付け |
has_many | 一対多で別のモデルを従属している関連付け |
has_one | 一対一で別のモデルを従属している関連付け |
has_many :through | 多対多で別のモデルと関連している 従属している第3のモデル(結合モデル)を介して、対象のモデルと多対多の関連付けになっている |
has_one :through | 一対一で複数のモデルと関連している 従属している第3のモデルが、対象のモデルを従属する関連付けになっている |
has_and_belongs_to_many | 多対多で別のモデルと関連している 結合モデルを使用せずに、対象のモデルと多対多の関連付けになっている(実際には結合用モデルが必要だが、結合用モデルは内部処理用) |
これらを適切に指定することで、前述したようにシンプルなプログラムにすることができるわけです。
関連するモデルに従属させるbelongs_to
「belongs_to」関連付けを行うことで、対象のモデルに従属することが指定されます。
「belongs_to」関連付けされたモデルには、対象モデルのidを保持するカラムがあり、データの内容はそのモデルに依存していることになります。
構文
belongs_to モデル名, スコープ, オプション
もし、複数のモデルに従属するモデルの場合は、「belongs_to」関連付けを複数行指定する必要があります。
例えば、「一対多」の項で紹介した図1のOrderモデルの場合、以下のようなプログラムコードになるでしょう。
class Order < ApplicationRecord
belongs_to :user
end
スコープとオプションは省略可能です。
スコープは、以下の構文でブロックを指定し、関連付けるために取り出すレコードを指定することができます。
-> {クエリメソッド}
ブロックには、whereやincludesなどのクエリメソッドを使用できます。例えば、特定のレコードだけを絞り込みたい場合は、以下のようにwhereを使用します。
-> {where item:’ring’}
オプションには以下のものがあります。
オプション名 | 説明 | 例 |
---|---|---|
autosave | 関連付けているモデルが保存されるたびに保存される | autosave:true |
class_name | 関連付けるモデル名を指定する ここでモデル名を指定することで、belongs_toの引数に任意の名称を設定することができ、追加されるメソッド名を変更することができる 必ずforeign_keyオプションも設定すること |
class_name:’user’ |
counter_cache | サイズ取得にキャッシュを利用するように指定する(SQLが発行されないため、処理効率が上がる場合がある) | counter_cache:true |
dependent | 関連付けしているモデルでレコードの削除が行われたときに、自動的に関連するレコードを削除するようにする | dependent: :destroy (destroyメソッドが実行される) dependent: :delete(destroyメソッドが実行されずに直接削除される) |
foreign_key | 関連付けるモデルを指す外部キーのカラム名を設定する このオプションを使用しなければ、「belongs_toの引数_id」が指定される |
foreign_key:’user_id’ |
primary_key | 関連付けるモデルのレコードを指定する主キーを設定する このオプションを指定しなければidカラム |
primary_key:’ex_id’ |
inverse_of | 自分のモデル名を指定する polymorphicオプションと同時に使用できない |
inverse_of:’order’ |
polymorphic | 指定したモデルに従属するのではなく、任意の数のモデルに従属することができるようにする 関連付けるモデル名を任意の名称(タイプ)にすることで、複数行のbelongs_toの設定が不要になる |
belongs_to:タイプ, polymorphic:true |
touch | 関連付けているモデルが保存されるたびにタイムスタンプを更新する | touch:true |
validate | 関連付けているモデルが保存されるたびに必ず検証される デフォルトはfalse |
validateoptional:true |
optional | 関連付けているモデルがあるかどうかの検証が行われなくなる デフォルトはfalse |
optional:true |
この中では特に利用するのは、dependentオプションでしょう。
このオプションを設定することで、親モデルのレコードが削除されることで、自動的に関連したレコードが削除されますので、関連が途切れたレコードが残る不整合を防ぐことができます。
ただし、複数のモデルと関連付けている場合などは、他との関連があるレコードを削除してしまうリスクがありますので、使用には注意が必要です。
また、class_nameオプションを使えば、関連するモデル名をプログラム上で変更することができますが、プログラムコードの可読性を犠牲にすることになりますので、あまりおすすめできません。
追加されるメソッド
「belongs_to」関連付けされたモデルには、以下のメソッドが追加されます。
メソッド名 | 説明 |
---|---|
モデル名 | 関連付けしているモデルを返す |
モデル名=(モデル) | 引数のモデルを関連付ける |
build_モデル名(カラム名:値,カラム名:値,,,) | レコードを生成して返す。メソッドを利用するオブジェクトの関連付けを生成したレコードのidに変更する テーブルに保存されない |
create_モデル名(カラム名:値,カラム名:値,,,) | レコードを生成して返す。メソッドを利用するオブジェクトの関連付けを生成したレコードのidにして返す カラムの検証にパスすればテーブルに保存される |
create_モデル名!(カラム名:値,カラム名:値,,,) | レコードを生成して返す。メソッドを利用するオブジェクトの関連付けを生成したレコードのidにして返す カラムの検証にパスしなければ例外が発生する |
※「モデル名」には、関連付けするモデル名が入ります
先ほど例に挙げた図1のOrderモデルには、以下のメソッドが追加されています。
@order.user
@order.user=
@order.build_user()
@order.create_user(name:”Tanaka”)
@order.create_user!(name:”Tanaka”)
https://web-camp.io/magazine/archives/12654
関連するモデルを持つhas_many
一対多の関連付けを明示するのが「has_many」です。
「has_many」指定を行うことで、対象となるモデルを従属することができます。
なお、従属するモデルには、必ず「belongs_to」が指定されている必要があります。
構文
has_many モデル名, スコープ, オプション
なお、「モデル名」は必ず複数形にしなければいけませんので、注意してください。
図1のUserモデルのプログラムコードは、以下のようになります。
class User < ApplicationRecord
has_many :orders
end
スコープとオプションは省略可能です。
スコープについては、「belongs_to」と同じくクエリメソッドを利用して、関連付けるレコードを指定することができます。
オプションについても、多くが「belongs_to」と同じですが、違うものもありますので、紹介しておきましょう。
オプション名 | 説明 | 例 |
---|---|---|
as | polymorphicな関連付けを行う場合の任意の名称(タイプ)の指定 | as:タイプ |
dependent | レコードを削除したときに、従属するレコードを削除するようにする | dependent: destroy (詳細な制御値は別表参照) |
source | has_many :through関連付けの関連付け元(従属するモデル)名を指定する | source:’item’ |
source_type | has_many :through関連付けでpolymorphicを利用する場合に、タイプを指定する | source_type:タイプ |
through | 「多対多」関連付けを行う場合に、経由する中間モデル名 | through:order |
・dependentの制御値による動作
dependentの制御値 | 動作 |
---|---|
:destroy | 従属するモデルのレコード削除時、従属するモデルのdestroyメソッドが実行される |
:delete_all | 従属するモデルのレコード削除は直接SQL実行される |
:nullify | 従属するモデルのレコードを削除せず、外部キーをNULL値にする |
:restrict_with_exception | 従属するモデルのレコードがある場合は例外を発生する |
:restrict_with_error | 従属するモデルのレコードがある場合は削除失敗し、レコードにエラー情報を付与される |
追加されるメソッド
「has_many」関連付けされたモデルには、以下のメソッドが追加されます。
メソッド名 | 説明 |
---|---|
モデル名 | 関連付けられたレコードをすべて返す |
モデル名<<(ActiveRecordオブジェクト,,,) | 従属するモデルに関連付けを追加する |
モデル名.delete(ActiveRecordオブジェクト,,,) | 従属するモデルの該当するレコードの外部キーをNULLにする |
モデル名.destroy(ActiveRecordオブジェクト,,,) | 従属するモデルの該当するレコードをdestroyメソッドで削除する |
モデル名=(ActiveRecordオブジェクト,,,) | 従属するモデルの該当するレコードを削除して、レコードを追加する |
モデル名の単数形_ids | 従属するモデルのid(主キー)の配列を返す |
モデル名の単数形_ids=(ids) | 従属するモデルの指定したid(主キー)を持つレコードだけを残して、残りを削除する |
モデル名.clear | 従属するモデルの全レコードを削除する 削除の方法はdependentオプションに従う |
モデル名.empty? | 従属するモデルにレコードがなければtrue |
モデル名.size | 従属するモデルのレコードの数を返す |
モデル名.find(主キー) モデル名.find([主キー,,,]) |
従属するモデルのレコードをidで検索する |
モデル名.where(カラム名: 値) | 従属するモデルのレコードをwhereで絞り込む すぐに実行されず、モデルへのアクセスが発生したタイミングで処理される |
モデル名.exists?(:カラム名 => 値) | 従属するモデルに指定した条件のレコードがあればtrue |
モデル名.build(カラム名:値,カラム名:値,,,) モデル名.build([{カラム名:値,カラム名:値,,,},,,]) |
メソッドを利用するオブジェクトのidを関連付けたレコードを生成して返す テーブルに保存されない |
モデル名.create(カラム名:値,カラム名:値,,,) | メソッドを利用するオブジェクトのidを関連付けたレコードを生成して返す カラムの検証にパスすればテーブルに保存される |
モデル名.create!(カラム名:値,カラム名:値,,,) | メソッドを利用するオブジェクトのidを関連付けたレコードを生成して返す カラムの検証にパスしなければ例外が発生する |
※「モデル名」には、関連付けするモデル(従属しているモデル)名の複数形が入ります
図1のOrderモデルの場合は、以下のメソッドが追加されています。
@user.orders
@user.orders<<(object, …)
@user.orders.delete(object, …)
@user.orders.destroy(object, …)
@user.orders=(objects)
@user.order_ids
@user.order_ids=(ids)
@user.orders.clear
@user.orders.empty?
@user.orders.size
@user.orders.find(1)
@user.orders.where(item:”ring”)
@user.orders.exists?(:item => “ring”)
@user.orders.build(item:”note”)
@user.orders.create(item:”note”)
@user.orders.create!(item:”note”)
多対多の関連漬け
「has_many」関連付けのthroughオプションや「has_and_belongs_to_many」関連付けを利用することで、多対多の関連付けを指定することができます。
has_many :through関連付け
多対多関連付けの中間にある結合モデルを利用するアプリケーションの場合に使用するのが、「has_many :through関連付け」です。
「has_many :through関連付け」の場合は、明示的に結合モデルを生成し、そのモデルを経由して対象と関連付けするという形をとります。
構文は以下のようになります。
has_many :結合モデル名
has_many :関連付け対象のモデル, through :結合モデル名
※関連付けするモデルの双方に指定します。
例えば、冒頭で紹介した図2の関連付けは、以下のようになります。
(図2)※再掲
class User < ApplicationRecord
has_many :orders
has_many :items, through: :orders
end
class Order < ApplicationRecord
belongs_to :user
belongs_to :item
end
class Item < ApplicationRecord
has_many :orders
has_many :users, through: :orders
end
「has_and_belongs_to_many」関連付け
「has_and_belongs_to_many」関連付けは、結合モデルを使用せず、多対多の関連付けされたモデルだけを利用する場合に使用します。利用頻度としては少ないと思いますが、紹介しておきましょう。
構文は以下のようになります。
has_and_belongs_to_many 関連付け対象のモデル
仮に、図2で結合テーブル(orderテーブル)を利用しないのであれば、以下のプログラムコードで多対多を指定できます。
class User < ApplicationRecord
has_and_belongs_to_many :orders
end
class Item < ApplicationRecord
has_and_belongs_to_many :orders
end
DMM WEBCAMPで本気でプログラミング学習をしてみる→
試しに作ってみる
ここから、代表的な関連付けを実際に指定して動かしてみましょう。
一対多
まずは一対多の関連付けです。冒頭に紹介した図1の関連付けをプログラムで実現します。
図1(再掲)
ここでは、scaffoldを利用して環境構築します。※scaffoldの詳細は<リンク>【Rails入門説明書】scaffoldについて解説を参照してください
以下のコマンドを実行してください。
[bash]rails new association_test1
cd association_test1
rails generate scaffold User name:string
rails generate scaffold Order user_id:integer item:string
rake db:migrate
[/bash]
※userモデルとorderモデルを生成しています。
ここでのポイントは、orderモデルのuser_idカラムです。
Railsは原則として名称を使って処理の関連を表現します。このカラムは、userモデルのidが格納されるカラムですので、「モデル名_id」でなければいけません。
しかし、このままではここにあるとおり、注文リストを表示しても数字が表示されるだけですので、ビューファイルを修正して、ユーザー名が表示されるようにします。
(app/views/orders/index.html.erb)
:
<tbody>
<% @orders.each do |order| %>
<tr>
<td><%= order.user.name %></td> <!– (1) –>
<td><%= order.item %></td>
<td><%= link_to ‘Show’, order %></td>
<td><%= link_to ‘Edit’, edit_order_path(order) %></td>
<td><%= link_to ‘Destroy’, order, method: :delete, data: { confirm: ‘Are you sure?’ } %></td>
</tr>
<% end %>
</tbody>
:
※(1)を修正してください
ここまでで環境ができましたので、関連付けの指定を行いましょう。
関連付けの指定は、関連付ける双方のモデルファイルに行います。
(app/models/user.rb)
class User < ApplicationRecord
has_many :orders
end
(app/models/order.rb)
class Order < ApplicationRecord
belongs_to :user
end
関連付けを確認するため、「rails console」で手動でデータを登録してみましょう。
[bash]rails console
[/bash]
で「rails console」を起動して、まずはユーザーを3名追加します。
[bash]User.create(name:”Oyama”)
User.create(name:”Yamada”)
User.create(name:”Kobayashi”)
[/bash]
サーバーを起動して「localhost:3000/users」にアクセスすれば、登録できていることが確認できます。
次に、いくつかの方法で注文を追加してみます。
[bash]Order.create(user_id:1,item:”ring”) # 注文の追加
@user = Order.find(1).create_user(name:”Kijima”) # 注文オブジェクトからユーザーを追加
@user.orders.create(item:”bike”) # ユーザーオブジェクトから注文を追加
[/bash]
関連付けをしているおかげで、注文リストにユーザー名が表示されています。
多対多
次は多対多の関連付けを作ってみましょう。
図2(再掲)
多対多の場合は結合モデルが必要ですので、モデルを3つ作ります。
[bash]rails new association_test2
cd association_test2
rails generate scaffold User name:string
rails generate scaffold Item title:string
rails generate scaffold Order user_id:integer item_id:integer
rake db:migrate
[/bash]
(app/views/orders/index.html.erb)
<tbody>
<% @orders.each do |order| %>
<tr>
<td><%= order.user.name %></td> <!– (1) –>
<td><%= order.item.title %></td> <!– (2) –>
<td><%= link_to ‘Show’, order %></td>
<td><%= link_to ‘Edit’, edit_order_path(order) %></td>
<td><%= link_to ‘Destroy’, order, method: :delete, data: { confirm: ‘Are you sure?’ } %></td>
</tr>
<% end %>
</tbody>
(app/models/user.rb)
class User < ApplicationRecord
has_many :orders
has_many :items, through: :orders
end
(app/models/order.rb)
class Order < ApplicationRecord
belongs_to :user
belongs_to :item
end
(app/models/item.rb)
class Item < ApplicationRecord
has_many :orders
has_many :users, through: :orders
end
rails console
User.create(name:”Okada”)
User.create(name:”Tanaka”)
User.create(name:”Kimura”)
Item.create(title:”book”)
Item.create(title:”computer”)
Item.create(title:”phone”)
Item.create(title:”ring”)
[/bash]
以下のように、ユーザーが直接商品を追加して注文できます。
[bash]@user = User.where(:name => “Kimura”).first # ユーザーオブジェクトを取得
@user.items.create(title:”note”) # ユーザーオブジェクトから商品を追加注文
[/bash]
既存の商品の注文も、注文テーブルを使用する必要がありません。
[bash]@item = Item.where(:title => “phone”).first # 商品オブジェクトを取得
@user.items << @item # 商品の注文
@user = User.where(:name => “Okada”).first # ユーザーオブジェクトを取得
@user.items << @item # 商品の注文
@user = User.where(:name => “Tanaka”).first # ユーザーオブジェクトを取得
@user.items << @item # 商品の注文
@item = Item.where(:title => “computer”).first # 商品オブジェクトを取得
@user.items << @item # 商品の注文
[/bash]
まとめ
今回は、has_manyの説明を中心にして、関連付けについて説明しました。
関連付けはRailsアプリケーションの肝の1つであるデータベース設計を実現する上でとても重要なものです。
そして、「has_many」や「belongs_to」を活用すれば、必要なメソッドが自動的に追加され、関連付けされたテーブルのデータを取得してくるなど、少し面倒な処理でも、とてもシンプルな実装をすることができます。
データベース設計を含めて、この関連付けはきちんとデータのつながりのイメージができるかどうかが重要になりますので、なんども挑戦して慣れていきましょう。
・関連付けを明示することで、プログラムコードがシンプルで読みやすくなる
・「belongs_to」で、対象のモデルに従属する(子モデルになる)。従属するには、対象のモデルのidを保持するカラムが必要
・「has_many」で、対象のモデルを従属させる(親モデルになる)。
・関連付けには、一対一、一対多、多対多の3種類がある。(一対一は、結果的に1つのテーブルにまとめることが多い)
・多対多を表現するためには、「has_many」のthroughオプションを使用する
DMM WEBCAMPは転職成功率98%※1の全コースオンライン対応の転職保証型のプログラミングスクールです。短期間で確実にスキルを身につけて、ひとりひとりに寄り添った転職サポートで、未経験からのエンジニア転職を叶えます!
外出自粛中でも、自宅にいながらオンライン学習でスキルを高めることができます。
キャリアに迷ったら、まずはビデオ通話で無料キャリア相談を受けてみませんか?
自宅で過ごす時間が増えた今こそキャリアアップを目指しましょう!この機会を活用し、ぜひDMM WEBCAMPの無料カウンセリングをご利用ください。
無料カウンセリングに申込む
【インタビュー】1ヶ月でRubyをゼロから学び、Webエンジニアとして転職!
ブラジルから帰国し技術をつけようとRubyエンジニアを目指してWebCampでRubyを学び、見事Webエンジニアとして転職を果たした田中さんにお話を伺いました。
「Rubyの学習がしたい。基礎をしっかりと理解したい」
「転職のサポートがほしい」
と考えている方はぜひお読み下さい。
https://web-camp.io/magazine/archives/8535
また転職ではなく、1からRubyなどのWEBサービス開発の知識を学びたい方は、DMM WEBCAMPのSKILLSコースがおすすめです。
詳細が分かる無料説明動画もあるので、ぜひご覧になってみてください!