【Rails】図解形式で理解する《アソシエーション解説書》2019年03月18日Railsアソシエーションとは?アソシエーションとは、テーブル同士の関連付け(リレーションシップ)をモデル上の関係として操作出来るようにする仕組みのことです。リレーションシップとは?リンクをコピーしましたリレーションシップとは、テーブル同士の関連付けのことです。商品をインターネット上で販売しているサイトを例にしてリレーションシップを確認してみます。各商品にはレビューが存在します。この関係性をテーブルで表現すると、productsテーブルにあるid列の主キーとreviewsテーブルにあるproduct_idカラムの外部キー制約によって実現することが出来ます。主キーとは、商品の情報を一意に識別するためのものです。外部キーとはどの商品にレビューが対応しているのか識別する為のものです。外部キー制約をつけたカラムには、対応する主キーの値を入れることで表現することが出来ます。外部キー制約の付いたカラム名は「参照するテーブル名(単数形)_id」にします。外部キー制約とは、他のテーブルのデータを参照出来る様にカラムにつける制約のことです。関連のあるテーブルにおいて、主キーを持つテーブルの事を参照先テーブルと呼び、外部キーを持つテーブルの事を参照元テーブルとも呼びますので覚えておきましょう。アソシエーションのコードを比較リンクをコピーしました先ほどのテーブル関係からid=1の商品(冷蔵庫)に属しているレビューを取得するにはどの様にすれば良いでしょうか?アソシエーションを使用しない場合と使用した場合でレビューを取得していきます。アソシエーションを使用しない場合リンクをコピーしましたアソシエーションを特に使用しない場合は、下記の様にproductsテーブルに対応するProductモデルでfindメソッドを利用して、id=1のレコードを取得します。外部キーの値は主キーの値が入っているので、whereメソッドに外部キーを指定してid=1の商品に属しているレビューを取得します。@product.id)アソシエーションを使用しない場合だと、外部キーを気にしてコードを記述しなければいけません。アソシエーションを使用する場合リンクをコピーしましたアソシエーションを使用する場合は、「Productオブジェクト.reviews」でその商品に属しているレビューを全て取得する事が出来ます。@product.reviewsこの様にアソシエーションを使用する事で、主キーや外部キーなどのテーブル同士の関連付けを直感的に記述する事が出来るのです。ER図でテーブル同士の関係性を整理リンクをコピーしましたアソシエーションを使う際に、テーブル同士の関係性を理解している必要があります。Diagram)というものを使います。productsテーブルの3つの関係性を例にしてER図で確認してみましょう。ER図は、IE表記法という書き方で記述されます。下記の表にある様に3つの記号を使ってテーブル同士の関係性を記述します。多3つのテーブルのER図では、ユーザーと商品の関係は「ユーザー1に対して商品が下限が0で上限が多」という意味になります。ユーザー1に対しての商品は0以上を表します。この様にER図を使うと、複数あるテーブル同士の関係を視覚的に整理する事が出来ます。belongs_toメソッドリンクをコピーしましたbelongs_toメソッドは、参照元テーブル(外部キーを持つテーブル)から参照先テーブル(主キーを持つテーブル)にアクセスする場合に定義します。belongs_toメソッドが必要な場合とは、どの様な関係性の事なのでしょうか?商品とレビューの関係性を参考にして確認していきましょう。belongs_toメソッドが必要な関係性とは?リンクをコピーしました先ほどの商品とレビューの関係性は、下記の図の様に複数のレビューが各商品に必ず属しています。レビューは商品がなければ存在しません。Product)」と言えます。この様な関係性の場合に、belongs_toメソッドを使います。to」は英語で「〜に属する」という意味です。belongs_toメソッドを定義するリンクをコピーしましたbelongs_toメソッドを定義するには、下記のコードの様にモデルクラスに定義します。:関連名end「モデル名はbelongs_toの引数に指定した関連名に属する」という意味になります。belongs_toメソッドを定義する事で、参照先(関連する主キーがあるテーブル)の情報が現在のモデルを経由して取得出来る様になります。belongs_toメソッドの引数の関連名は単数形になるので注意してください。Product)」という関係性を定義していきます。商品のテーブルはproductsテーブル、レビューのテーブルはreviewsテーブルです。reviewsテーブルの外部キーproduct_idで対応するproductsテーブルのレコードを参照します。複数のレビューは、1つの商品に属しているので下記の様にbelongs_toメソッドを定義する事が出来ます。:productendbelongs_toメソッドをReviewモデルに定義し引数にproductsテーブルに対応するモデル名を記述します。この様な関係を1:nと言います。商品1つに対してレビューは0以上あるのでnと表現しています。1:nの関係は、has_manyメソッドを使って定義する事が出来ます。has_manyメソッドを定義するリンクをコピーしましたhas_manyメソッドは、下記のコードの様に1:nの1のモデルクラス側に定義します。:関連名end上記は、「1つのモデルは複数の関連名を持つ」という意味になります。has_manyメソッドの引数の関連名は複数形になるので注意してください。Reviews)」という1:nの関係性をhas_manyメソッドで定義します。:reviewsend上記は、1つのproductオブジェクトに対して、複数のReviewオブジェクトが存在しますという意味になります。複数のオブジェクトなので関連名(reviews)は複数形になっています。Productモデル経由でReviewモデルの情報を取得するリンクをコピーしましたモデルクラスにhas_manyメソッドを定義したことによって、Productモデル経由でReviewモデルの情報を取得出来る様になりました。商品id=1(冷蔵庫)のレビューを全て取得する例をみていきましょう。商品id=1(冷蔵庫)のオブジェクトを取得します。オブジェクトを取得endそして、情報を取得するには「オブジェクト.関連名」で関連するオブジェクトを取得する事が出来ます。冷蔵庫に関連するレビューを取得する為に「@product.reviews」を実行すると、複数のレビューのレコードが配列に入って返されます。05:31:52">]>これは、1つの商品は複数のレビューを持つというhas_manyの関係なので、返り値は複数のレビューが存在します。1つの商品である冷蔵庫には複数のレビューが存在している為、下記の様にeachメソッドで1つ1つレビューを取り出します。%></ul>上記のコードの実行結果は、下記の様に表示されます。今回のhas_manyの関係では、1つの商品に関連するレビューを取得すると、複数のレコードが返りましたが、belongs_toの関係である1つのレビューが属している商品を取得する場合の返り値は単一のオブジェクトが返ります。05:23:40">中間テーブルとthroughオプションリンクをコピーしましたpikawakaは、どの様なメンバーと部署で構成されているのか見てみましょう。勇弥さんは、開発部と記事部の責任者です。これは1人のメンバーが複数の部署に属していると言えます。次に開発部の部署を見てみると複数のメンバーがいます。1つの部署には複数のメンバーが属していると言えます。メンバーは複数の部署を持ち、部署も複数のメンバーを持つというお互いに1つに対して複数存在している関係を多対多の関係と呼びます。多対多をテーブル間のリレーションで表現するには?リンクをコピーしました先ほどのメンバーと部署の多対多の関係をテーブルで確認します。メンバーのテーブルをusersテーブル、部署のテーブルはdepartmentsテーブルとします。宮嶋さんは開発と記事の2つの部署に所属しています。一方で、開発部は宮嶋さんと高橋さんが所属しているので、1人のユーザーは複数の部署に所属できるし、1つの部署は複数の人が入れるので、ユーザーと部署は多対多の関係と言えます。この様な多対多の関係をテーブルにする場合に、中間テーブルというテーブルが必要になります。belongs_toでは、1つの外部キーを使って参照元テーブルから参照先テーブルを見ていましたが、中間テーブルでは、usersテーブルとdepartmentsテーブルの2つのテーブルをお互いに参照する必要があります。それには、中間テーブル(authorizations)に、usersテーブルを参照するための外部キー制約のカラム(user_id)とdepartmentsテーブルを参照するための外部キー制約のカラム(department_id)を持たなければなりません。中間テーブルの名前ですが、昔はusersテーブルとdepartmentsテーブルの名前を使ってusers_departmentsという名前にしていましたが、現在はauthorizationsテーブルの様な分かりやすいテーブル名を自分で付けることが出来ます。中間テーブルが必要な理由リンクをコピーしました多対多の関係の時に中間テーブルがなくても設計することは可能ですが、カラム数が多くなったり、使われないカラムが出てくるという良くないDB設計になってしまいます。usersテーブルとdepartmentsテーブルの多対多の関係に中間テーブルを置かない場合は、お互い外部キーを置く必要があります。このままでは下記の様に1カラムに複数のデータが入ってしまいます。原則1カラムには1つのデータしか持つ事が出来ない為、下記の様にカラムを増やして対応しなければなりません。departmentsテーブルの方は、開発部署の人数が増える度にカラムを人数分用意しなければいけなくなります。このままでは下記の様に使用されないカラムが出てきてしまいます。この空カラムにはNULLが入りますが、この様なNULLが沢山入るDB設計はアンチパターンという良くない設計とされます。部署の人数や1人のメンバーが属する部署が増えれば増える程、未使用のカラムが多い設計となります。良いDB設計をするためには、カラムにNULLが入らない様な設計にする必要があります。これを実現するのが中間テーブルなのです。中間テーブルを使うと、下記の様に空のカラムは無くなります。中間テーブルを使わない場合のデメリットカラム数が多くなる未使用(NULL)のカラムが多くなるNULLが沢山入ってしまうアンチパターンという良くない設計になる多対多の関連を定義するリンクをコピーしました多対多の関連を定義するには、モデルクラスにhas_manyメソッドのthroughオプションを使います。throughは、「経由する」という意味なので中間テーブルを経由して関連先のオブジェクトを取得することが出来ます。:中間テーブル」は、throughオプションを使う時に必要なおまじないだと覚えて置きましょう。メンバーと部署の多対多を定義するリンクをコピーしました先ほどのメンバーと部署の多対多の関係をhas_manyメソッドのthroughオプションを使って定義していきます。usersテーブルに対応するUserモデルのファイルに定義していきます。:authorizations」は、「1つのUserオブジェクトはAuthorizationモデルを経由して複数のDepartmentオブジェクトを持っている」という意味になります。#おまじないendDepartmentモデルも同様にhas_manyメソッドのthroughオプションを定義していきます。#おまじないendauthorizationsテーブルの外部キーは、usersテーブルとdepartmentsテーブルを参照するために存在します。その為、中間テーブルであるauthorizationsテーブルに対応するAuthorizationモデルには、belongs_toメソッドを定義します。:departmentendそしてauthorizationsテーブルですが、外部キーのカラム以外にもroleというカラムがあります。Authorizationは権限という意味です。authorizationsテーブルのroleカラムには、「責任者」か「メンバー」の値が入ります。これは、メンバーの部署での役割が責任者なのか、部署のメンバーという意味になります。authorizationsテーブルにroleカラムを持たせる理由リンクをコピーしましたroleカラムは、usersテーブルで良さそうですが、usersテーブルにするとメンバーが複数の部署に所属していた場合に役割(role)は1つしか指定出来なくなります。勇弥さんの役割(role)には1つしか入れることが出来ないのです。勇弥さん(id=1)の部署のレコードを取得する例を見ていきましょう。勇弥さんの所属する部署のレコードを全て取得しています。08:21:02">]>このコードの実行時の流れは、まず中間テーブルのuser_idを見にいき、user_idが1のレコードのdepartment_id(1と2)を参照してdepartmetsテーブルの1と2のレコードを取得します。コードが実行される時、中間テーブルのauthorizationsテーブルはあくまでも経由するだけで実際に取得しているのはdepartmentsテーブルのレコードだという事に注意してください。部署から部署に属するメンバーも取得することが出来ます。08:19:42">]>usersテーブルからdepartmentsテーブルのレコードを取得していた時と同様に、コードが実行される時、中間テーブルのauthorizationsテーブルはあくまでも経由するだけです。実際に取得しているのはusersテーブルのレコードです。SQLで確認してみようリンクをコピーしました先ほどの部署に所属するメンバーを取得するコードをもう一度確認してみましょう。08:19:42">]>実は、4行目の「department.users」では下記の様なSQLが発行されているのです。JOINでテーブルが結合されるまでを確認します。(理解しやすい様にSELECT文で全てのカラムを取得する様にしています。JOINは、ON以降に記述された結合条件にしたがってusersテーブルをauthorizatinsテーブルを結合します。このSQLにWHERE文を追加してみます。このWHERE文は、authorizationsテーブルのuser_idが1のレコードに絞り込みます。WHERE文によってレコードが絞り込まれたのが分かります。この状態ではusersテーブル以外のauthorizationsテーブルのカラムがまだ含まれていますね。現在のSELECT文ではテーブル結合を分かりやすくする為に全てのカラム(*)を取得していたので、元のSELECT文に戻して必要なカラムのみを取得します。作成されたテーブルは、authorizationsテーブルを通して部署に対応するメンバーのレコードを取得している事が分かりました。今回のコードを再度確認してみましょう。08:19:42">]>「department.users」で関連先のインスタンスを取得出来ていたのは、下記の様なSQLが裏側で発行されていたからでした。1この様なSQLによって、中間テーブルのアソシエーションを使って関連先のテーブルのインスタンスを取得することが出来るのです。has_oneメソッドリンクをコピーしましたhas_oneメソッドは、1対1の関係を定義します。1対1の関係とは、どのような関係のことなのでしょうか?例えば、メンバーの宮嶋さんはPikawakaのアカウントを1つ持っています。アカウントを複数持つことはありません。これは、1人のメンバーが1つのアカウントを持っている関係です。この様な関係を1対1の関係と言い、has_oneメソッドで定義する事が出来ます。1対1をテーブル間のリレーションで表現するには?リンクをコピーしました1対1の関係をテーブル間で表現するには、参照元テーブルに外部キー制約の付いたカラムを持たせる事で表現することが出来ます。先ほどのメンバーとアカウントの1対1の関係をテーブルで確認していきましょう。メンバーのテーブルをusersテーブル、アカウントのテーブルをaccountsテーブルとします。この場合の参照先テーブルがusersテーブル、参照元テーブルがaccountsテーブルなので、accountsテーブルには、外部キー制約の付いたuser_idカラムを持たせる事で参照できる様になります。補足説明1対1の関係は同等の関係なので、usersテーブルとaccountsテーブルのどちらに外部キーを持たせるか悩みどころですが、主従関係で考えてみてください。アカウントのないユーザーはありえますが、ユーザーのいないアカウントはありえません。よって、usersテーブルが主でaccountsテーブルが従となるので、accountsテーブルに外部キーを持たせます。has_oneメソッドを定義するリンクをコピーしましたhas_oneメソッドを定義するには、主従関係の主にあたるモデルの方に定義します。単数形endhas_oneメソッドは、1対1の関係を定義します。これによって関連付くオブジェクトは1つになるので、関連名は単数形になります。Userモデルにhas_oneメソッドを定義しようリンクをコピーしましたそれでは、先ほどの例を参考にしてhas_oneメソッドを定義しましょう。usersテーブルに対応するのはUserモデルになるので、下記の様にhas_oneメソッドを定義します。account)でした。ユーザーに関連するアカウントは1つなので、関連名は単数形のaccountになります。accountsテーブルに対応するモデルはAccountモデルになるので、下記の様にbelongs_toメソッドを定義します。endbelongs_toメソッドの引数の関連名は、1つのアカウントは1人のユーザーに属しているので、単数形になります。ユーザーの持つアカウントを取得してみようリンクをコピーしましたhas_oneメソッドを関連付けしたので、usersテーブルのname='宮嶋勇弥'さんの持つアカウントを取得して、account_numberを表示させます。controllerでユーザー情報をfind_byメソッドを使って取得します。'宮嶋勇弥')endユーザーオブジェクトを取得したら、view側で「オブジェクト.関連名」を指定して関連するオブジェクトを取得します。%></p>has_oneメソッドのアソシエーションは、あるユーザーに関連するアカウントは1つだけなので、「@user.account」@userの持つアカウントを取得することが出来ます。今回はaccount_numberを表示したいので、「@user.account.account_number」の様に記述します。アソシエーションで利用可能なオプションリンクをコピーしました基本的なテーブルを作成し命名規則に従っていればオプションを利用する事は少ないですが、独自の命名を行っている場合にアソシエーションのオプションを利用する事でカスタマイズする事が出来ます。オプションの種類は沢山ありますが、belongs_toメソッド、has_oneメソッド、has_manyメソッドで利用することが出来る主要なオプションについて解説していきます。オプションの解説では、has_one/belongs_to関係にある「usersテーブルとaccountsテーブル」とhas_many/belongs_to関係にある「usersテーブルとblogsテーブル」を参考にしていきます。この3つのテーブルの関係をアソシエーション定義すると、下記の通りになります。:userendforeign_keyオプションリンクをコピーしましたforeign_keyとは、外部キー制約のカラム名を指定することが出来るオプションです。「_id」の形式になり外部キーだと認識されます。「_id」の形式ではなく、違う名前の形式で外部キーに名前をつける場合は、foreign_keyオプションで明示的に宣言する必要があります。foreign_keyオプションは、下記の様に定義します。'外部キー名'endusersテーブルを参照するaccountsテーブルの外部キー制約のカラム名は「user_id」でした。これを「user_no」にカラム名だけを変更して、usersテーブルのid=1に関連するアカウントを取得してみます。1「user.account」を実行した際に、エラーが発生しています。_id」の形式が外部キーだと認識されてしまうので、foreign_keyオプションを追加する必要があります。下記の様に、foreign_keyオプションに変更したカラム名「user_no」を指定します。追加end先ほどのコードを実行すると、foreign_keyオプションによって「user_no」が外部キーと認識されて、エラーが起こらずにユーザーに関連するアカウントを取得する事が出来ています。05:46:40">この様に、外部キー名を変更してユーザーに関連するアカウントを取得する場合は、Userモデルにforeigh_keyオプションで外部キー名を指定する必要があります。次に、accountsテーブルのid=1が属しているユーザーを取得してみましょう。nilusersテーブルには、id=1のアカウントに関連するレコードが存在しているはずなのに「account.user」を実行すると、nilが返ってきます。これは、アカウントに関連するユーザーを参照する際にも、foreign_keyオプションでuser_noが外部キー制約のカラムと明示的に宣言する必要があるからです。試しに、foreign_keyオプションに変更したカラム名「user_no」を指定して先ほどのコードを実行してみます。08:18:59">この様に、Accountモデルにforeign_keyオプションで変更した外部キー名を明示的に宣言することによって、アカウントに関連するユーザーを取得することが出来ました。ポイントforeign_keyとは、外部キー制約のカラム名を指定することが出来るオプションのことです。_id」の形式が外部キーだと認識されてしまいます。外部キー制約のカラム名を変更する場合は、カラム名だけではなくforign_keyオプションで各モデルに宣言する必要があります。class_nameオプションリンクをコピーしましたclass_nameとは、関連名を変更する為に、クラス名を明示的に宣言するオプションです。関連名とは、関連するテーブルを参照する際のメソッド名になります。このメソッド名を変更するためには、class_nameオプションで関連するモデルのクラス名を指定する必要があります。class_nameオプションは、下記の様に定義します。one関係にあるaccountsテーブルの例を見ながら確認していきます。usersテーブルのid=1(宮嶋さん)に関連するaccountsテーブルのデータを参照するには、下記の様にUserモデルのhas_oneのアソシエーションで指定した関連名(account)で参照する事が出来ます。%><p>accountの関連名ではなく、違う名前を使ってアカウントを参照する場合は、下記の様にclass_nameオプションを使って定義します。'Account'endこれによって「@user.account」ではなく、「@user.pikawaka_account」で関連するアカウントを参照する事が出来る様になりました。%><p>foreign_keyオプションとclass_nameオプションリンクをコピーしましたforeign_keyオプションとclass_nameオプションは、併用して使うことが出来ます。2つのオプションを併用しなければいけない場面とはどの様な場面なのでしょうか?それでは、「pikawakaのメンバーを管理するusersテーブル」と「pikawakaの記事を管理するblogsテーブル」を例にして確認していきます。2つのテーブルと定義されているアソシエーションは下記の通りでした。:userendこのアソシエーションによって、pikawakaのメンバーの宮嶋勇弥さん(usersテーブルのid=1)の書いた記事を全て取得することが出来ます。記事を書いた人を取得する事も出来ます。nil>]>pikawakaでは、1つの記事に対して必ずレビューをする担当者が1人います。現在のblogsテーブルの構成ではblogsテーブルのid=1(rubyとは?)の記事を書いた人が宮嶋さんというデータしか取得する事が出来ません。記事のレビューをした人を取得するには、blogsテーブルに記事を書いた人と記事をレビューする人を参照することが出来るように、外部キー制約の付いたカラムを2つ置く必要があります。このようにすると、blogsテーブルのid=1(rubyとは?)の記事を書いた人が宮嶋さん、レビューをした人は高橋さんということが分かります。下記のようにusersテーブルのid=1(宮嶋さん)の書いた記事を全て取得する事や、レビューした記事を全て取得する事が出来ます。「_id」の形式が外部キーだと認識されます。詳しくはforeign_keyオプションを参照)ここでは、分かりやすくする為にデフォルトの外部キー名user_idを使わず「記事を書いた人(外部キー1)」のカラム名をwriter_id、「レビューをした人(外部キー2)」のカラム名をreviewer_idにします。このままではwriter_idとreviewer_idは外部キーのカラムだと認識されないので、下記の様にUserモデルとBlogモデルのアソシエーションにforegin_keyオプションを使ってwriter_idとreviewer_idが外部キーだと明示的に宣言する必要があります。#追加endforeign_keyオプションで定義したことによって、writer_idとreviewer_idが外部キーと認識されましたが、下記の様にユーザーに関連する記事にアクセスする際に:blogsの関連名が同一になっているので、書いた記事を取得したいのか、レビューした記事を取得したいのか判断することが出来ません。この状態でUserモデルのインスタンスに関連する記事を取得しようするとエラーが起こります。(Blogモデルでも関連名の:userが同じ為エラーが起こります。1どちらの記事を取得するのか、記事を書いた人なのかレビューした人なのか判断する為にclass_nameオプションを利用して、同一の関連名を変更します。wrote_blogs、レビューした記事にアクセスする関連名を:reviewed_blogsに変更します。#class_name追加endclass_nameオプションで関連名を変更したことによって、エラーなくユーザーに関連する書いた記事とレビューした記事を区別してアクセスすることが出来る様になりました。下記はid=1の宮嶋勇弥さんの書いた記事とレビューした記事を取得する例です。nil>]>この様に関連する記事が書いた記事なのか、レビューした記事なのか判断して複数のデータを取得することが出来ています。Blogモデルも記事を書いた人か、記事をレビューした人かを区別させる為に、class_nameオプションを使います。レビューした人にアクセスする関連名を:reviewerに変更します。#追加end下記は、blogsテーブルのid=1の「rubyとは?」の記事を書いた人とレビューした人を取得しています。User)なので返り値が複数ではなく、Userモデルのインスタンスを返しているという点にも注目してください。08:19:54">この様にclass_nameオプションとforegin_keyオプションを併用して使うことによって、1つのUserモデルに対して複数の役割を持たせて参照することが出来ます。dependentオプションリンクをコピーしましたdependentとは、親モデルを削除する時に関連付けされている子モデルの挙動を決めるオプションで、下記の様に定義します。:オプションの種類end下記は、dependentオプションの主な種類です。nullが入る事によって、レコードを残しつつ関連は解消させる。コールバックは実行されないdestoryメソッドやdeleteメソッドとの違いは、【Rails】destoryメソッドの使い方を徹底解説!を確認してください。親モデルを削除する際に、dependentオプションで関連付けされている子モデルに対しての挙動を決めておかないとエラーが起こります。エラーが発生するこれは、blogsテーブルのid=1,2のuser_idカラムの値が、usersテーブルに存在しないidなので、blog.userとしてもnilが返るためです。:destroyをつけておく方が安全です。上記の場合は、tableの外部キーにforeign_keyを貼っていない場合です。fails)が起きます。:destroyが必要になるのです。今回は関連するblogsテーブルのレコードを保持しておく必要がないので、:destroyをdependentオプションに指定します。:destroyを追加します。:destroyが追加された状態で、usersテーブルのid=1(宮嶋さん)のレコードを削除します。08:18:59">user.destroyを実行すると、下記のレコードを削除するという3つのDELETE文が発行されます。fails)が起こらないように、子モデル(Blog)のデータから削除されています。blogsテーブルのid=1blogsテーブルのid=2usersテーブルのid=1usersテーブルのid=1のデータと関連する2つの記事のデータが削除されてから、ユーザーのデータが削除されている事が確認出来ました。:destroyを追加してしまうと、記事を削除した際に記事を書いたユーザーも削除されるので注意してください。ユーザーが書いた記事は削除した1記事だけではなく他にも存在するのでエラーが起こるので注意してください。タイミングで実行されるメソッドをコールバックと呼びますが、これを使わない場合は不必要なクエリが走らない様に:destroyではなく:deleteを使いましょう。まとめアソシエーションによって、テーブル同士の関連付けをモデル上の関係として操作する事が出来ます。belongs_toメソッドは、他のモデルに属している事を定義します。has_manyメソッドは、他のモデルを複数持っている事を定義します。throughオプションは、他のモデルと多対多の関係の繋がりを定義します。