Rails の ActiveRecord で1対多のテーブルを作る方法


例えば店舗情報を持つ、Shop モデルと、店舗の写真を持つ Photo モデルがあって、
Shop に対して、1対多で Photo モデルを関連付けしたい

開発環境


Rails 3.0.9

サンプルコード

class Shop < ActiveRecord::Base
end


class Photo < ActiveRecord::Base
end


まず、所有先を格納するために、photoテーブルに、shop_id を追加する必要がある

所有する側の定義


has_many

class Shop < ActiveRecord::Base
  has_many :photos # 複数形であること
end

所有される側の定義


belongs_to

class Photo < ActiveRecord::Base
  belongs_to :shop
end

has_many で追加されるメソッドの例

shop = Shop.find(...)
shop.each ...        # 関連する情報については、配列と同じような制御ができる
# それぞれ、実行のタイミングでDBに記録される
shop.photos << photo # 写真追加
shop.delete(photo)   # 写真関連取消(photoの、shop_id に NULL が入る)
shop.clear           # 写真全関連取消(photoの、shop_id に NULL が入る)

belongs_to で追加されるメソッドの例

photo = Photo.find(...)
photo.shop        # 店舗情報取得
photo.shop = shop # 店舗情報を格納
photo.save        # save しないとDBに入らない

find の include オプション

shop = Shop.find(...).first
shop.photos.each


とかやると、each で、関連する情報にアクセスする毎に、クエリが走るのでとても非効率
find の時に、include オプションをつけると、関連するテーブルの情報もまとめて取得してくれるので、効率が良い

Shop.find(:all, :include => :photo, ...)

モデルを削除した時に、関連するモデルも削除したい場合


dependent オプションを使う

dependent オプションを付けない場合の挙動
photo = Photo.new(:name => 'photo_one')
shop = Shop.new(:name => 'shop_one')
photo.save; shop.save;
shop.photos << photo
shop.destroy


DBを見るとshopは削除されたが、photo は、shop_id に値があるままで残っている

sqlite> select * from photos;
id    shop_id        name  crea  upda
----  -------------  ----  ----  ----
2     4              photo_one  2012-04-03 08:06:12.897753  2012-04-03 08:15:46.275918
dependent オプションをつけた場合の挙動
class Shop < ActiveRecode::Base
  # destroy 実行時に、関連するモデルにも、destroyを実行させる
  has_meny :photos, :dependent => :destroy
end


この場合、shop.destroy 時に、photo も削除される


destroy 以外に、delete オプションもあるけど、今のところ使わないので、解説したリンクだけ貼っとく