0
0

【Rails】いいね機能の実装ー中間テーブルとはなにか?ー

Posted at

はじめに

初学者向けのRails講座には、大抵の場合いいね機能の実装が含まれていると思います。
ただ、いいね機能を実装するための”中間テーブル”は、概念的で理解しにくいところがあるため、自分の整理のためにもアウトプットします。

前提

  • インスタ風のアプリを作成
  • userがpostを投稿する感じ
  • 他のuserのpostにいいねできる機能を作りたい

何がしたいのか

  • いいね機能を実装したい
  • postの一覧画面(post/index.html)からいいねできるようにしたい

どうする必要があるのか

  • 中間テーブル(likesテーブル)を作成する
  • 一意性制約をつける
  • likesテーブルを通していいねしたPostを取得できるようにする
  • routesの工夫

実装方法

中間テーブルを作る

  1. 普段通りrails g model Likeでモデルを作る
  2. マイグレーションファイルを編集
    1. reference でuserとpostを追加
    2. 複合インデックスを追加し、一意性制約をつける
  3. rails db:migrateの実行

マイグレーションファイルの内容

class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      t.references :user, null: false, foreign_key: true
      t.references :post, null: false, foreign_key: true
      t.timestamps
    end
  
  add_index :likes, [:user_id, :post_id], unique: true
  end
end

中間テーブルとは?

以下のようなものだと理解しています。

  • 2つのテーブルの間ある
  • 2つのテーブルの関係性を記述する
  • 具体的な値を持たない

今回のケースで言うと以下のようになります

  • usersテーブルとpostsテーブルの間にlikesテーブルを作る
  • likesテーブルのカラムは、idとuser_id(外部キー)とpost_id(外部キー)のみ
  • likesテーブルはuser_id=1のユーザーが、post_id=1のポストをいいねしているという関係性のみを表現している

中間テーブルがあることで、以下のことができるようになります

  • user側から:自分がいいねしたpostの一覧を取得できる
  • post側から:特定のpostをいいねしているuserの一覧を取得できる
    ※実際のuse case的にはuser側がメインになるかなと思います

複合インデックスとは

  • インデックスとは、検索速度を早めるためにデータベースに索引を作成すること
  • referenceを使うと、自動的単一のインデックスが作成させる
  • 今回の場合は、user_idとpost_idはほぼ必ずセットで使うので、user_idとpost_idをセットでインデックス化すると便利
  • add_index :likes, [:user_id, :post_id]の箇所で実現している

一意性制約とは

  • 特定のカラムの内容をUniqueにする制約
  • よくある例でいうと同じユーザー名は使えないとか、同じメールアドレスだと"すでに登録されています"というアラートが出るとか
  • 今回の場合は、特定のユーザーが特定のポストにいいねできる回数を1回にしたいので一意性制約をつける
  • unique: trueで実現
  • より具体的に言うと、user_id=1 ー post_id=1 というセットがデータベースに存在すると、いいねしようとしてもできなくなる ということ

modelファイルに関係性を記述する

likeモデルの記述

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
end
  • likeモデル側から見ると、userにもpostにも紐づいているので上記のような記述になる

userモデルの記述

  has_many :likes, dependent: :destroy
  has_many :liked_posts, through: :likes, source: :post
  • has_many :likes, dependent: :destroyこれはいつもどおりの記述
  • has_many :liked_posts, through: :likes, source: :post こちらがポイント
  • User.first.liked_postsでUser.firstがいいねしたポストを取得できるようになる
  • through: :likes はそのままlikesテーブルを経由して という記述
  • source: :post で何を取得したいのかを記述
  • おそらくlikesテーブルに外部キーが2つ設定されているため、user側からのリクエストに対してuser_idカラムを検索し、同じ行にあるpost_idを返して、postsテーブルから該当のポストを引き出すという動作を行っていそう

ほぼ重複になるのであえて書きませんが、postモデル側でも似たような記述をすることで、post側でもも特定のポストをいいねしたuserの一覧を取得できるようになる

routesの記述

  resources :posts, only: [:new, :create, :index] do
    resource :like, only: [:create, :destroy, :show]
  end

なぜネストするのか?

  • リンクとして、post/:post_id/likeの形だと扱いやすいため
  • 流れとしてはuserがpostにlikeする
  • あるポストに対して、いいねに対するリクエストを送る、というのがわかりやすいというか自然

なぜresources :likesではなく、resource :likeなのか

  • あるユーザーにとって、特定のポストに対するいいねは1つだけだから
  • つまりpost/:post_id/like/idのidは不要
  • post/:post_id/likeの形にすることで、deleteする際にidを指定しなくて良いので、処理の記述も短く済む
0
0
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
0
0