takecian

Diary

23 Dec 2013

Rails で Model に 多対多の関連をもたせる

WEBサービスによくある「お気に入り機能」を作ってみる。

ユーザー(お気に入りにする側)対商品(お気に入りにされる側)という
Model 同士に多対多の関連を持たせる、という話。

これも1対多の関連の時と同じように belongs_to と has_many メソッドを使う。
子から親に参照をしたい場合に belongs_to を使って、
親から子を参照したい場合に has_many を使う。

今回は関連を持たせるためのテーブルを別で用意して、
has_many の方には through をつけて、この関連テーブル(Favoriteテーブル)を指定する。

[code lang="ruby"]
class Item < ActiveRecord::Base
has_many :favorites, dependent: :destroy
has_many :users, through: :favorites
end

class User < ActiveRecord::Base
has_many :favorites, dependent: :destroy
has_many :items, through: :favorites
end

class Favorite < ActiveRecord::Base
belongs_to :item
belongs_to :user
validates :user_id, presence: true
validates :item_id, presence: true
end
[/code]

読み出すときは、

[code lang="ruby"]
- item.users.each do |user|
= link_to(user) do
= cl_image_tag(user.image, :width => 250, :height => 250, :radius => 20, :fetch_format => :png, :q => 100, :effect => :shadow, :alt => user.name)
%h3= user.name
[/code]

とか

[code lang="ruby"]
- user.items.each do |item|
.thumbnail.item
= link_to(item) do
= cl_image_tag(item.thumbnail.path, :width => 250, :height => 250, :radius => 20, :fetch_format => :png, :q => 100, :effect => :shadow, :alt => item.name)
.caption
%h3= item.name
%span.wrap= item.copy
[/code]

とする。

今回から erb から haml を使うようにしました。
#erb から haml への乗り換えは erb2haml という gem を使うのが便利。

参考:
Rails の ERB を Haml に変換する[Qiita]

お気に入りの情報を設定する時は、controller 上で関連を作るようにしておいて、

[code lang="ruby"]
class FavoritesController < ApplicationController
...
def create
puts item_params[:item_id]
@item = Item.find(item_params[:item_id])
@user = current_user
current_user.favorite!(@item)
respond_to do |format|
format.html { redirect_to current_user }
format.js
end
end
...
end

class User < ActiveRecord::Base
def favorite!(item)
favorites.create(item_id: item.id)
end
end

[/code]

view からは非同期で呼ぶよう(form に "remote: true"をつける)にする。

[code lang="ruby"]
= form_for(current_user.favorites.build(item_id: item.id), remote: true) do |f|
%div= f.hidden_field :item_id
= f.submit "食べたい", class: "btn btn-large btn-primary"
[/code]