読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Rails4で多対多のリレーションをモデルに実装する

Rails初級 Rails Model

Railsでは、ActiveRecordのhas_manyhas_and_belongs_to_manyを使うことで、DBの「多対多」のテーブル間の関連をモデルに簡単に実装することができます。

has_manyhas_and_belongs_to_manyの違い
多対多関係を作るには、中間テーブルを作成する必要があります。
has_manyの場合、中間テーブルを表現するクラスを作らないといけないが、その中間テーブルに属性やバリデーションなどを追加できます。
has_and_belongs_to_manyの場合、中間テーブルのクラスを作らなくて良いが、中間テーブルにカスタマイズができません。

個人的には、そこまで手間ではないので、has_manyで拡張性をもたせた方がよいと思います。

動作確認

  • Rails 4.1
  • ActiveRecord 4.1

目次

  1. 多対多関連とは
  2. マイグレーションファイルを作成する
  3. モデルにhas_manyとbelongs_toを追加する
  4. 使えるようになるメソッド


1. 多対多関連とは

説明のために次のER図を実装してみます。
f:id:nipe880324:20140810044509p:plain:w480

1商品は複数のカテゴリに属するので、「商品」から見ると「カテゴリ」は"多"です。
1つのカテゴリには、多数の商品が存在するので、「カテゴリ」から見ると「商品」も"多"です。
このような関係を「多対多関係」といいます。




2. マイグレーションファイルを作成する

それぞれのテーブルを普通に作成し、最後に中間テーブルに両方のテーブルの外部キーを定義して作成します。

Categoryモデルとcategoriesテーブルの作成します。

rails g model Category name:string
rake db:migrate


Productモデルとproductsテーブルの作成します。

rails g model Product name:string price:integer
rake db:migrate


has_manyの場合、CategoryProductモデルとcategories_productsテーブルの作成します。

rails g model CategoryProduct category_id:integer product_id:integer
rake db:migrate


has_and_belongs_to_manyの場合、categories_productsテーブルを作成します。

rails g migration create_categories_products category_id:integer product_id:integer
rake db:migrate


3. モデルにhas_manyとbelongs_toを追加する

ではモデルファイルに多対多関連の宣言を追加します。

has_manyの場合

# app/models/category.rb
class Category < ActiveRecord::Base
  has_many :category_products
  has_many :products, through: :category_products

  # throughオプションによりcategory_products経由でproductsにアクセスできるようになる
  # 具体的には、category.productsで商品にアクセスができる
end

# app/models/category_product.rb
class CategoryProduct < ActiveRecord::Base
  belongs_to :category
  belongs_to :product
end

# app/models/product.rb
class Product < ActiveRecord::Base
  has_many :category_products
  has_many :categories, through: :category_products

  # throughオプションによりcategory_products経由でcategoriesにアクセスできるようになる
  # 具体的には、product.categoriesでカテゴリにアクセスができる
end


has_manyメソッドには次のようなオプションを指定できます。

  • class_nameオプションで関連するモデルのクラス名を指定でき、関連名と参照先のクラス名を異なるものにできできる。
  • foreign_keyオプションで参照先を参照する外部キーの名前を指定できる。デフォルトは、参照先のモデル名_id
  • dependentオプションで親オブジェクトが削除された時の扱いを指定できる。destroydelete_allなどが指定可能。
  • asオプションでポリモフィック関連を定義できる。
  • throughオプションでモデル接続の関連を設定できる。

など


has_and_belongs_to_manyの場合

# app/models/category.rb
class Category < ActiveRecord::Base
  has_and_belongs_to_many :products
end

# app/models/product.rb
class Product < ActiveRecord::Base
  has_and_belongs_to_many :categories
end


4. 使えるようになるメソッド

これらを追加することで自動的に次のようなメソッドが使えるようになります。

has_manyの場合

# 作成
category1 = Category.create(name: "カテゴリ1") # category単体で作成
product1 = Product.create(name: "商品1", price: 1000) # product単体で作成

category1.products.create(name: "商品2", price: 200) # category1に関係したproductを作成

# リレーション
category1.products.count # => 1
category1.products << product1 # product1をcategory1に関連させた
category1.products.count # => 2

product1.categories # => product1に関連しているcategoryの配列

# 中間テーブル
CategoryProduct.all # category_productsの全てのレコードを取得
# CategoryProductモデルが存在するため、中間テーブルのバリデーションや属性の追加などができる

has_and_belongs_to_manyの場合

# 作成
category1 = Category.create(name: "カテゴリ1") # category単体で作成
product1 = Product.create(name: "商品1", price: 1000) # product単体で作成

category1.products.create(name: "商品2", price: 200) # category1に関係したproductを作成


# リレーション
category1.products.count # => 1
category1.products << product1 # product1をcategory1に関連させた
category1.products.count # => 2

product1.categories # => product1に関連しているcategoryの配列

以上です。