【Rails】お気に入り機能


実装したい機能

  • 投稿表示ページに、お気に入りボタン設置
  • 記事投稿者でない、ログイン中のユーザーのみお気に入り登録できる。
  • 記事投稿者、未ログインは、お気に入り登録できない。
  • お気に入りボタンをクリックしたら、Ajax通信でデータ保存。
  • ユーザーのマイページに、お気に入りリスト一覧を表示。

実装の手順

  1. お気に入りテーブル作成(ユーザー/投稿テーブルの中間テーブルに相当)
  2. ルーティング: 記事投稿のルーティングにネストの形で記述。
  3. お気に入りコントローラー: create、destroyアクションを定義。
  4. Userコントローラー: ユーザーのマイページで呼び出す
  5. ビュー: 投稿表示ページに、お気に入りボタン設置。マイページに、お気に入り一覧表示。

1. お気に入りテーブル作成

  • 誰が、どの記事に、お気に入りしたかを管理したいので、favoriteモデル(user_id、post_idカラム)を生成。
ターミナル
% rails g model Favorite user:references post:references  # Favoriteモデル作成
% rails db:migrate        # 下記のマイグレーション実行
マイグレーションファイル
class CreateFavorites < ActiveRecord::Migration[5.0]
  def change
    create_table :favorites do |t|
      t.references :user, foreign_key: true, null: false
      t.references :post, foreign_key: true, null: false
      t.timestamps
    end
  end
end

アソシエーションを組む。
ユーザー、記事が削除されたら、お気に入りも削除(dependent: :destroy オプション)。

user.rb
has_many :favorites, dependent: :destroy     # ユーザー/お気に入り → 1:多
post.rb
belongs_to :user, optional: true
has_many :favorites, dependent: :destroy     # 記事/お気に入り → 1:多
favorite.rb
belongs_to :user   # ユーザー/お気に入り → 1:多
belongs_to :post   # 記事/お気に入り    → 1:多

validates_uniqueness_of :post_id, scope: :user_id    # バリデーション(ユーザーと記事の組み合わせは一意)
# 同じ投稿を複数回お気に入り登録させないため。

2. ルーティング

  • 記事詳細表示のルーティングにネスト(createとdestory)。
  • マイページのルーティングにネスト。
routes.rb
# マイページのルーティングにネスト
  resources :users, only: [:show, :edit, :update] do
    get :favorites, on: :collection
  end

# 記事詳細表示のルーティングにネスト
  resources :posts, expect: [:index] do
    resource :favorites, only: [:create, :destroy]
  end

3、 4. コントローラーの定義

  • 誰が?: ログイン中のユーザー(current_user)に限定。投稿者本人は除外。
  • どの投稿に?: 自分が投稿していない投稿に対して。
  • お気に入りコントローラー: create、destroyアクションを定義。
  • Userコントローラー: ユーザーのマイページで呼び出す
favorites_controller
  before_action :set_post
  before_action :authenticate_user!   # ログイン中のユーザーのみに許可(未ログインなら、ログイン画面へ移動)

  # お気に入り登録
  def create
    if @post.user_id != current_user.id   # 投稿者本人以外に限定
      @favorite = Favorite.create(user_id: current_user.id, post_id: @post.id)
    end
  end
  # お気に入り削除
  def destroy
    @favorite = Favorite.find_by(user_id: current_user.id, post_id: @post.id)
    @favorite.destroy
  end

  private
  def set_post
    @post = Post.find(params[:post_id])
  end
user_controller
  def show
    @user = User.find(params[:id])
    @posts = @user.posts

    favorites = Favorite.where(user_id: current_user.id).pluck(:post_id)  # ログイン中のユーザーのお気に入りのpost_idカラムを取得
    @favorite_list = Post.find(favorites)     # postsテーブルから、お気に入り登録済みのレコードを取得
  end

5. ビュー

  • 投稿表示ページに、お気に入りボタン設置。
  • お気に入り登録/削除でAjax通信(remote: true で実装)。
  • Ajax通信で、お気に入り登録数の表示を更新。登録済みか?でボタンcssも変更させる。
favorites/_favorite.html.haml
#favorite{ id: @post.id }
  - if !Favorite.exists?(user: current_user, post_id: @post.id)   # お気に入り未登録の時
    = link_to post_favorites_path(@post), method: :post, remote: true, class: "favorite-btn favorite-post" do
      .i.far.fa-thumbs-up
        %span いいね!
        = @post.favorites.length   # お気に入り登録数
  - else    # お気に入り登録済みの時
    = link_to post_favorites_path(@post), method: :delete, remote: true, class: "favorite-btn favorite-delete" do
      .i.far.fa-thumbs-up
        %span いいね!
        = @post.favorites.length   # お気に入り登録数
  • remote: true : link_to や form_for/form_tag、button_to などに使えるオプション。
    データは、コントローラーへ送信し、Ajax通信してくれる。ページ移動ではなく、JSを探してくれる(ページ移動ストップ)。
    お気に入りボタンクリックで、発火させるJSの動作を_favorite.html.hamlと同じ階層に作成(Ajax通信で呼び出す)。
favorites/create.erb
$("#favorite_<%= @post.id %>").html("<%= j(render partial: 'favorites/favorite', locals: { post: @post }) %>");
  <!-- j メソッドで、JSとして認識してくれて、非同期で更新してくれる -->
favorites/destroy.erb
$("#favorite_<%= @post.id %>").html("<%= j(render partial: 'favorites/favorite', locals: { post: @post }) %>");
  • マイページに、お気に入り一覧表示。
users/show.html.haml
- if @favorite_list.present?
  - @favorite_list.each do |post|
    = post.title
    = post.content