今回もレシピアプリを例にお気に入り機能を実装していきます。
完成イメージ
お気に入り機能の実装
モデルの作成
お気に入りに必要な情報は以下の通りになります。
・どの投稿をお気に入りしたかの情報(レシピ情報)
・誰がお気に入りしたかの情報(ユーザ情報)
この2つの情報を保存するためにlikesテーブルを作成します。
rails g model likes
上記のコマンドで作成されたマイグレーションファイルを以下のように編集します。
references型でuserとrecipeに紐付けます。
class CreateLikes < ActiveRecord::Migration[6.0]
def change
create_table :likes do |t|
t.references :user, foreign_key: true #追記
t.references :recipe, foreign_key: true #追記
t.timestamps
end
end
end
rails db:migrate
これでlikesテーブルが作成されました。
アソシエーションの設定
UserモデルとLikeモデルの関係は
・ユーザーは複数お気に入り登録することができる
・とあるお気に入り(たとえばlikesID=1)にはユーザーが一人しかいない
となるので
class User < ApplicationRecord
has_many :recipes, dependent: :destroy
has_many :likes #追記
# 以下略
class Like < ApplicationRecord
belongs_to :user #追記
end
RecipeモデルとLikeモデルの関係は
・1つの投稿(レシピ)は複数お気に入りを持つことができる
・とあるお気に入り(たとえばlikesID=1)に紐づく投稿(レシピ)は1つしかない
となるので
class Recipe < ApplicationRecord
belongs_to :user
has_many :likes, dependent: :destroy #追記
# 以下略
class Like < ApplicationRecord
belongs_to :user
belongs_to :recipe #追記
end
バリデーションの設定
ユーザーが1つの投稿に対してお気に入り登録できる回数を1回にします。
つまり、user_idとrecipe_idの組み合わせが重複しないように設定します。
uniquenessヘルパーを使って重複していないか検証します。Railsガイド
class Like < ApplicationRecord
belongs_to :user
belongs_to :recipe
validates :user_id, uniqueness: { scope: :recipe_id } #追記
end
ルーティングの追加
今回、追加するルーティングはお気に入り情報を保存(create)するルーティングと削除(destroy)するルーティングになります。
また、postsに対してlikesは子の関係になるので、ネストさせどの投稿に紐づくかわかるようにします。
Rails.application.routes.draw do
# 中略
# ここから編集
resources :recipes do
resources :likes, only: [:create, :destroy]
end
# ここまで編集
end
コントローラーの作成、編集
お気に入り機能に使うコントローラーを作成します。
先ほど、ルーティングでlikesと指定したのでコントローラー名をlikesとします。
rails g controller likes
次にアクションを追加していきます。
親モデルに属する子モデルのインスタンスを新たに生成するのでbuildを使います。
class LikesController < ApplicationController
before_action :authenticate_user!
def create
@like = current_user.likes.build(like_params)
@recipe = @like.recipe
if @like.valid?
@like.save
redirect_to recipe_path(@recipe)
end
end
def destroy
@like = Like.find(params[:id])
@recipe = @like.recipe
if @like.destroy
redirect_to recipe_path(@recipe)
end
end
private
def like_params
params.permit(:recipe_id)
end
end
インスタンスメソッドの作成
ビューファイルを編集する前に現在サインインしているユーザーがお気に入り登録しているかどうか判断するためのメソッドを作成します。
find_byでuser_idとrecipe_idが一致するlikeを探し、なければnilを返します。
class Recipe < ApplicationRecord
belongs_to :user
has_many :likes, dependent: :destroy
# 中略
# ここから追加
def liked_by(user)
Like.find_by(user_id: user.id, recipe_id: id)
end
# ここまで追加
end
ビューファイルの編集
current_user != @recipe.userで自分の投稿には表示しないようにし、条件分岐に先ほど作成したliked_byメソッドを使い表示させるボタンを変化させます。
また、link_toメソッドはデフォルトではgetなのでmethod:を指定します。
# 中略
<div class="recipe-name">
<%= @recipe.title %>
</div>
<div>
投稿者: <%= @recipe.user.name %>さん
</div>
<div class="recipe-content">
カテゴリー: <span class="recipe-category"><%= @recipe.category.name %></span>
所要時間: <span class="recipe-time"><%= @recipe.time_required.name %></span>
</div>
# ここから追加
<% if current_user != @recipe.user %>
<div class="recipe-like">
<% if @recipe.liked_by(current_user).blank? %>
<%= link_to "お気に入りに保存", recipe_likes_path(@recipe), method: :post, class: "btn btn-success btn-sm" %>
<% else %>
<%= link_to "保存済み", recipe_like_path(@recipe,@recipe.liked_by(current_user)), method: :delete, class: "btn btn-outline-success btn-sm" %>
<% end %>
</div>
<% end %>
# ここまで追加
# 以下略
以上でお気に入り登録機能が実装できました。
挙動を確認してみましょう。
お気に入りに登録したレシピを表示させよう!
ユーザーマイページにお気に入りに登録したレシピを表示させます。
ルーティングの設定
まずは、お気に入り一覧ページのルーティングの設定をしてます。
Rails.application.routes.draw do
# 中略
# ここから編集
resources :users do
member do
get :like
end
end
# ここまで編集
# 以下略
コントローラーの編集
whereメソッドでlikesテーブルから自分のidが登録されているレコードを取得し、pluckメソッドで取得したレコードからrecipe_idを配列の形で取得します。
class UsersController < ApplicationController
# 中略
# ここから追加
def like
@user = User.find(params[:id])
likes = Like.where(user_id: current_user.id).pluck(:recipe_id)
@likes = Recipe.find(likes)
end
# ここまで追加
end
ビューファイルの作成、編集
まずはビューファイルを作成します。
touch app/views/users/like.html.erb
ビューファイルを以下のように編集します。
<div class="like-index text-center">
<div class="user-name">
<%= @user.name %>さんのお気に入り
</div>
<% if @likes.length != 0 %>
<% @likes.each do |recipe| %>
<div class="recipe-contents d-flex">
<% if recipe.images.present? %>
<%= image_tag recipe.images[0], class: "index-img" %>
<% else %>
<%= image_tag "no_image.png", class: "index-img" %>
<% end %>
<div class="recipe">
<div class="recipe-title">
<%= link_to recipe.title, recipe_path(recipe) %>
</div>
<div class="recipe-content">
カテゴリー: <span class="recipe-category"><%= recipe.category.name %></span>
所要時間: <span class="recipe-time"><%= recipe.time_required.name %></span>
</div>
</div>
</div>
<% end %>
<% else %>
お気に入りはまだ登録されていません
<% end %>
</div>
if文でお気に入り登録を1つもしていない場合は「お気に入りはまだ登録されていません」と表示させるようにしています。
それでは実際に表示してみましょう。
お気に入り登録していない場合
お気に入り登録している場合
以上で完成です。