1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails】お気に入り機能を実装しよう!

Last updated at Posted at 2021-03-15

今回もレシピアプリを例にお気に入り機能を実装していきます。

完成イメージ

a2b9460dbe7757921f96c6dd75b8b6cb.gif

お気に入り機能の実装

モデルの作成

お気に入りに必要な情報は以下の通りになります。
・どの投稿をお気に入りしたかの情報(レシピ情報)
・誰がお気に入りしたかの情報(ユーザ情報)

この2つの情報を保存するためにlikesテーブルを作成します。

ターミナル
rails g model likes

上記のコマンドで作成されたマイグレーションファイルを以下のように編集します。
references型でuserとrecipeに紐付けます。

db/migrate/2021xxxxxxxxxx_create_likes.rb
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)にはユーザーが一人しかいない
となるので

app/models/user.rb
class User < ApplicationRecord
  has_many :recipes, dependent: :destroy
  has_many :likes #追記
# 以下略
app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user #追記
end

RecipeモデルとLikeモデルの関係は
・1つの投稿(レシピ)は複数お気に入りを持つことができる
・とあるお気に入り(たとえばlikesID=1)に紐づく投稿(レシピ)は1つしかない
となるので

app/models/recipe.rb
class Recipe < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy  #追記
# 以下略
app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :recipe  #追記
end

バリデーションの設定

ユーザーが1つの投稿に対してお気に入り登録できる回数を1回にします。
つまり、user_idとrecipe_idの組み合わせが重複しないように設定します。
uniquenessヘルパーを使って重複していないか検証します。Railsガイド

app/models/like.rb
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :recipe

  validates :user_id, uniqueness: { scope: :recipe_id }  #追記
end

ルーティングの追加

今回、追加するルーティングはお気に入り情報を保存(create)するルーティングと削除(destroy)するルーティングになります。
また、postsに対してlikesは子の関係になるので、ネストさせどの投稿に紐づくかわかるようにします。

config/routes.rb
Rails.application.routes.draw do
# 中略
# ここから編集
  resources :recipes do 
    resources :likes, only: [:create, :destroy]
  end
# ここまで編集
end

コントローラーの作成、編集

お気に入り機能に使うコントローラーを作成します。
先ほど、ルーティングでlikesと指定したのでコントローラー名をlikesとします。

ターミナル
rails g controller likes

次にアクションを追加していきます。
親モデルに属する子モデルのインスタンスを新たに生成するのでbuildを使います。

app/controllers/likes_controller.rb
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を返します。

app/models/recipe.rb
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:を指定します。

app/views/recipes/show.html.erb
# 中略
<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 %>
# ここまで追加
# 以下略

以上でお気に入り登録機能が実装できました。
挙動を確認してみましょう。
a2b9460dbe7757921f96c6dd75b8b6cb.gif

お気に入りに登録したレシピを表示させよう!

ユーザーマイページにお気に入りに登録したレシピを表示させます。

ルーティングの設定

まずは、お気に入り一覧ページのルーティングの設定をしてます。

config/routes.rb
Rails.application.routes.draw do
# 中略
# ここから編集
  resources :users do
    member do
      get :like
    end
  end
# ここまで編集
# 以下略

コントローラーの編集

whereメソッドでlikesテーブルから自分のidが登録されているレコードを取得し、pluckメソッドで取得したレコードからrecipe_idを配列の形で取得します。

app/controllers/users_controller.rb
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

ビューファイルを以下のように編集します。

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つもしていない場合は「お気に入りはまだ登録されていません」と表示させるようにしています。
それでは実際に表示してみましょう。

お気に入り登録していない場合

2f7a05068e22dd26473b723a27082aef.png

お気に入り登録している場合

a555f7fed86f55f5613c9bab77ec753e.png

以上で完成です。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?