中間テーブルを用いた多対多の関係(その 5)

前回からの続きです。モデル同士のアソシエーションを定義します。

class Customer < ActiveRecord::Base
  has_many :sales
  has_many :products, :through => :sales
end
class Product < ActiveRecord::Base
  has_many :sales
end
class Employee < ActiveRecord::Base
  has_and_belongs_to_many :branches
  belongs_to :manager, :class_name => 'Employee', :foreign_key => 'mgr_id'
  has_many :assistants, :class_name => 'Employee', :foreign_key => 'mgr_id'
  has_many :sales
  scope :belongs, lambda { |b| joins(:branches).where(:branches => { :name => b }) }
end
class Branch < ActiveRecord::Base
  has_and_belongs_to_many :employees
end
class Sale < ActiveRecord::Base
  belongs_to :product
  belongs_to :employee
  belongs_to :customer
end

中間テーブルの話からは逸れますが、Employee クラスを見ていただいてわかるとおり、アソシエーションはテーブル名と一致しないものも作れます。その場合は対応するモデルと結合に利用する外部キーを指定する必要があります。

EmployeeBranch の関係は以前にやったのと同じ、外部キーしか持たない中間テーブルを利用した多対多の関係です。

注目すべきは CustomerProduct の関係です。CustomerSale は一対多の関係にありますが、ProductSale が一対多の関係にあることを利用して CustomerProduct の多対多の関係を持たせています。その場合は has_many を使いますが

has_many :products, :through => :sales

のように経由する中間テーブルを指定する必要があります。また、中間テーブルのデータとなるモデルに対するアソシエーションも併せて定義しておく必要があります。
実際の利用例です。
app/controllers/customer_controller.rb

class CustomersController < ApplicationController
  ...

  def show
    @customer = Customer.find(params[:id])
  end

  ...
end

app/views/customers/show.html.erb

...

<h2>購入した商品</h2>
<ul>
  <% @customer.products.uniq.each do |p| %>
  <li><%= p.name %></li>
  <% end %>
</ul>

...

出力例は次回に。