カリキュラム学習・ポートフォリオ作成中、この機能を実装するのに苦戦したので、メモ。
非同期通信にしたかったのは以下の二点。
前提条件
テーブル
schema.rb
create_table "users", force: :cascade do |t|
(略)
end
create_table "products", force: :cascade do |t|
(略)
end
create_table "favorites", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "product_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
モデル(アソシエーション)
favorite.rb
belongs_to :user
belongs_to :product
user.rb
has_many :favorites, dependent: :destroy
product.rb
has_many :favorites, dependent: :destroy
def favorited_by?(user)
Favorite.where(user_id: user.id).exists?
end
ルート
rutes.rb
resources :products do
resource :favorites, only: [:create, :destroy]
end
実装①(商品のお気に入り追加・削除)
productsコントローラは以下の通り
products_controller.rb
def show
@product = Product.find(params[:id])
end
次にお気に入りボタンをパーシャル化。(userディレクトリ内に作成)
views/users/products/_favorite_button.html.erb
<% if product.favorites.where(user_id: current_user.id).exists? %>
<%= link_to "お気に入りから削除", users_product_favorites_path(product_id: product.id), method: :delete, remote: true %>
<% else %>
<%= link_to "お気に入りに追加", users_product_favorites_path(product_id: product.id), method: :post, remote: true %>
<% end %>
ここでremote: true
を付けることで、非同期通信が可能になる。
products/show.html.erbで呼び出し
views/users/products/show.html.erb
<div id="favorites_buttons_<%= @product.id %>">
<%= render 'users/products/favorite_button', product: @product %>
</div>
次はfavoritesのコントローラ。
fovorites_controller.rb
def create
@product = Product.find(params[:product_id])
favorite = current_user.favorites.new(product_id: @product.id)
favorite.save
end
def destroy
@product = Product.find(params[:product_id])
@favorites = current_user.favorites
favorite = current_user.favorites.find_by(product_id: @product.id)
favorite.destroy
end
ここでリダイレクト先を指定しなければ、JSの処理を探しに行ってくれる。
最後に作成・削除した場合のJS処理を作成する。
views/users/favorites/create.js.erb
$("#favorites_buttons_<%= @product.id %>").html("<%= j(render 'users/products/favorite_button', product: @product) %>");
views/users/favorites/destroy.js.erb
$("#favorites_buttons_<%= @product.id %>").html("<%= j(render 'users/products/favorite_button', product: @product) %>");
show.html.erb内の、idで設定した範囲のみが、処理後書き換えられるようになる。
実装②(お気に入り一覧から削除)
fovoritesのコントローラは以下の通り作成
favorites_controller.rb
def index
@favorites = current_user.favorites
end
非同期通信したい範囲をパーシャル化。
view/users/favorites/_form.html.erb
<% if favorites.present? %>
<table class="table">
<thead>
<tr>
<th colspan="3">商品</th>
</tr>
</thead>
<tbody>
<% favorites.each do |f| %>
<tr>
<td>
<%= attachment_image_tag f.product, :image, :fill, 80, 80, format: 'jpeg', fallback: "no_image.jpg", size: '80x80' %>
</td>
<td><%= link_to f.product.name, users_product_path(f.product.id) %></td>
<td><%= link_to "お気に入りから削除", users_product_favorites_path(product_id: f.product.id), method: :delete, remote: true %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<h3>
お気に入りリストがありません。<br>
商品ページから追加してみましょう
</h3>
<% end %>
fovorites/index.html.erbで呼び出し
<div class="row">
<div class="col-sm-6 offset-3" id="favorites_index">
<%= render 'users/favorites/form', favorites: @favorites %>
</div>
</div>
JSの処理に以下の1行を追加
views/users/favorites/destroy.js.erb
$("#favorites_index").html("<%= j(render 'users/favorites/form', favorites: @favorites) %>");
これでお気に入り一覧にも非同期機能が実装できた。
実装した上での反省点・感想
- JSに記載しているインスタンス変数はそれぞれcreate・destroyメソッドから確認しているため、そっちでも定義してあげないといけないということが身にしみた。(考えてみれば当たり前だったが)
- 実装①についてはググればよく出てくるが、②は見かけないので自力で
解けて感動した。ただ流れを理解していれば迷うとこでも無かったなと感じた。