Railsアプリでお気に入り機能を実装したい!


はじめに

現在railsでアプリを作成していてお気に入り機能を実装しました。
その備忘録としてアウトプットしたいと思います!

完成イメージ

前提

  • Deviseでユーザー管理機能を実装済み
  • ユーザーに紐づくモデルを作成済み(tweetモデルやarticleモデルなど)
  • テンプレートエンジンはhamlを採用

開発環境

OS: macOS Catalina
データベース: Mysql
バックエンド: Ruby on Rails 6.0
テキストエディタ: Visual Studio Code

目次

1.データベース設計
2.ルーティングの記述
3.アソシエーションの記述
4.コントローラーの記述
5.Viewの記述

1. データベース設計

さきにER図を御覧ください。

今回はUserテーブルEventテーブルFavoriteテーブルで考えたいと思います。
Eventテーブルとそのカラムについては任意の設定で大丈夫です。

FavoriteテーブルはUserテーブルとEventテーブルの中間テーブルとなります。
そのため、カラムにはuser_idとevent_idを持たせます。
これで、どのユーザーがどのイベントをお気に入りしたかをデータベースに保存できます。

それでは、この設計に沿って準備をしていきます!

下準備

ターミナル
$rails g model Favorite user_id:integer tweet_id:integer
$rails g controller favorites create destroy
$rails db:migrate #忘れずに

マイグレーションファイルはこんな感じ!


次はルーティングの記述です

2. ルーティングの記述

config/routes.rb
 #=====================================
  root 'events#index'
  devise_for :users

  resources :events do
    resource :favorites, only: [:create, :destroy]
  end
  #======================================

ポイントは2つです

  • favoritesアクションはeventアクションにネストさせる
    こうすることで、お気に入りされたイベントの情報を引き出すのが楽になります

  • favoritesアクションはresorcesではなくresorceを用いる
     favoriteはユーザーみたいに詳細ページや編集ページがいらないので個別のidはいりません。
     なので省略するためにresorceを用います

3. アソシエーションの記述

favoriteはuserとeventに属する側なのでbelongs_toで記述します

app/modelfavorite.rb
class Favorite < ApplicationRecord
    belongs_to :user
    belongs_to :event
end

userとeventモデルは以下のように記述します

app/model/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  has_many :tweets
  has_many :favorites
  has_many :favorite_events, through: :favorites, source: :event 
end
app/model/event.rb
class Event < ApplicationRecord
    belongs_to :user
    has_many :favorites
end

User.rbに関して

source

source「参照元のモデル」をさすオプションです。
これを指定することでアソシエーションでメソッドチェーンする時の名称を変更することができます。

本当はhas_many :events, through: :favorites
と記述したいのですが、上のhas_many :eventsと重複してしまうため、favorite_eventsと名称を変更しています。

has many throughを使った「ユーザーがファボしたイベントの表示」
これで、@user.favorite_eventsとやることで、「ユーザーがファボしたイベント」を取得することができるようになります。

更に、お気に入り機能は登録だけでなく解除の機能も必要です。
そのため、Eventモデルに「このイベントをユーザーがファボしているかどうか」を判定するメソッドを用意します。

ユーザーがイベントをお気に入りしたかどうかの判定メソッド

app/model/event.rb
class Event < ApplicationRecord
    belongs_to :user
    has_many :favorites

# 追加
    def already_favorited?(user) #引数を受け取るように設定
        favorites.where(user_id: user.id).exists?
    end
end

普通メソッドはコントローラーの中に記述していくと思います。
僕自身、モデルにはアソシエーションやバリデーションだけを書く場所だと認識していましたがメソッドも定義することができます。

余談ですが、このようにモデルに定義するメソッドをインスタンスメソッドと呼びます。
インスタンスメソッドはクラスで生成されたインスタンスにメソッドを設定できます。

例えば、events_controller.rbが下記の場合を見てみましょう

app/controllers/events_controller.rb
def show
  @event = Event.find(params[:id])
end

この場合の@eventEventクラスで生成されたインスタンスにあたります。

この後のViewのセクションでも登場しますが@event.already_favorited?(current_user)と記述することで、現在ログインしているユーザーが対象のeventをお気に入りしているかどうかの判定をすることができるようになります!

4. コントローラーの記述

UsersControllerの記述

app/controllers/users_controllers.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
    @events = @user.events
    @favorite_events = @user.favorite_events
  end
end

@favorite_events = @user.favorite_eventsと記述することで
Viewでeachメソッドを使用することで「ユーザーがファボしたイベント」を取得することができます。

FavoritesControllerの記述

app/controllers/favorites_controller.rb
class FavoritesController < ApplicationController
  def create
    favorite = current_user.favorites.build(event_id: params[:event_id])
    favorite.save
    redirect_back(fallback_location: root_path)
  end

  def destroy
    favorite = Favorite.find_by(event_id: params[:event_id], user_id: current_user.id)
    favorite.destroy
    redirect_back(fallback_location: root_path)
  end
end

createアクション

アソシエーションを組んでいるので、current_user.favoritesとすることでfavoriesテーブルのuser_idにcurrent_user.idを入れることができます。あとはevent_idを入れるのですが、、、

ポイントはbuildです。newでもcreateでも問題ないですが、インスタンスに紐づくインスタンスを生成するときにbuildが形式的に使われるのでこのように記述します。

createは自動的にsaveまで行いますが、buildはnewと同じく生成するだけなのでfavorite.saveを記述してあげます。

redirect_back

いいね、もしくはいいねを解除したときに同じページのままでいてほしいのでこのように記述します。
redirect_backで戻るべきページがなくてエラーがおきてしまう可能性があるので(fallback_location: root_path)と記述してエラーになるのを防ぎます。

destroyアクション

特になしです笑
redirect_backに関してはcreateアクションと同じくいいねを解除した後も同じページに遷移するように記述しておきます。

5. Viewの記述

冒頭の通りHTMLではなくhamlで記述します。

お気に入りボタンの実装

app/views/events/show.html.haml
- if @event.already_favorited?(current_user)
  %p.favorite-heading
     保存をキャンセル
   = link_to event_favorites_path(@event), method: :delete, class: "favorite-link" do
     = icon("fas", "star", class: "favorite-icon")
- else
  %p.favorite-heading
     イベントを保存
   = link_to event_favorites_path(@event), method: :post, class: "favorite-link" do
     = icon("far", "star", class: "favorite-icon")

解説

  • - if @event.already_favorited?(current_user)

user.rbに定義したalready_favorited?(user)メソッドをViewで使用します。
引数としてcurrent_userを渡すことで、現在ログインしているユーザーが対象イベントをファボしているかどうかの条件分岐をしています。

もし、すでにファボしている場合はdestroyアクションをファボしていなければcreateアクションを実行します。

  • = link_to~

pathはrails routesで確認してタイプミスがないように気をつけましょう。
メソッドもしっかり記述しましょう。

地味ですが、最後のdoは意外と躓いたポイントです。今回はアイコンを押してファボしたいので要素全体をリンクにするためにこのように記述します。

アイコンはFont Awesomeでスターを使用します。
Font Awesomeの導入の仕方はこちらをご覧ください

以上で実装はおわりです!!

おわりに

最後まで読んでいただきありがとうございました!
改良点としては、いいねを非同期通信にできたらもうすこしUI/UXの点でよくなると思います。
お疲れさまでした。。

参考文献

【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】🎸