Railsで簡単なLike機能(いいね)を実装する
こんにちは、わったんです。
Twitterで定番のLike機能の実装を勉強しましたので、ここで整理しておきます。基本は下のブックマーク機能実装の記事を参考にしてます。
①モデルの作成・編集
Like機能は「一人のuser→複数のpostにLikeする」、「一つのpost→複数のuserからLikeされる」特徴を持つのでuserとpostが「多対多」の関係になります。このような場合、中間テーブルを作成します。中間テーブル作成のメリットなどはこちら。
$ rails g model Like user:references post:references
中間テーブルはuserテーブルもpostテーブルも参照するので、外部キー制約を両方のカラムにつけます。そしてマイグレーションファイルの編集。
202202211214222_create_likes.html.erb
class CreateLikes < ActiveRecord::Migration[5.2]
def change
create_table :likes do |t|
t.references :user, null: false, foreign_key: true
t.references :post, null: false, foreign_key: true
#null: falseを付与
t.timestamps
end
add_index :likes, [:user_id, :post_id], unique: true
#user_idとpost_idの組み合わせが一意になるように(複合キー)設定
end
end
rails db:migrate
これでマイグレーションが完了しました。次はLikeモデルの編集です。
Like.rb
class Like < ApplicationRecord
belongs_to :user
belongs_to :post
validates :user_id, uniqueness: { scope: :post_id}
end
ユニーク制約にscopeをつけることで一つのpostに同じuserが重複してlikeできないようにします。もしscopeなしだと、userがあるpostにlikeしたら、そのuserは他のpostにはlikeできなくなる機能になってしまいます。
Post.rb
belongs_to :user
has_many :likes, dependent: :destroy
has_many :users, through: :likes
validates :title, presence: true, length: { maximum: 255 }
validates :content, presence: true, length: { maximum: 65_535 }
end
has_many :likesにはthroughオプションでlikesを指定することで、中間テーブルを経由するようになります。
User.rb
class User < ApplicationRecord
authenticates_with_sorcery!
has_many :posts, dependent: :destroy
has_many :likes, dependent: :destroy
has_many :likes_posts, through: :likes, source: :post
validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }
validates :email, uniqueness: true
validates :email, presence: true
validates :first_name, presence: true, length: { maximum: 255 }
validates :last_name, presence: true, length: { maximum: 255 }
has_many :likes_posts, through: :likes, source: :postで中間テーブルを経由してpostモデルを参照する架空のモデルを設定することができます。参考にしたぺージはこちら。少しrails consoleで確認してみます。
pry(main)> puts user3.likes_posts
Post Load (0.1ms) SELECT "posts".* FROM "posts" INNER JOIN "likes" ON "posts"."id" = "likes"."post_id" WHERE "likes"."user_id" = ? [["user_id", 4]]
#<Post:0x00007feea4d6e1e8>
user3にはidが4のオブジェクトが入っています(少しややこしくて、ごめんなさい)。userオブジェクト.likes_postsを実行してみると、likesテーブルからuser3のidが登録されているレコードを選び出し、次にpostテーブルのidとlikesテーブルのpost_idが等しいpostレコードを取り出していることがわかります。through, sourceを設定することでこのようなSQL文を作ることができるのですね。
またuser.rbに以下も追加します。
user.rb
#userオブジェクトのidとpostやlikeオブジェクトのuser_idが同じかどうかを判断
def mine?(object)
object.user_id == id
end
#likes_postsテーブルにpostオブジェクトを追加する。
#いいねを押したときに、いいねしたユーザーといいねされた投稿の情報が保存される。
def like(post)
likes_posts << post
end
#likes_postsテーブルから引数のpostオブジェクトに該当するレコードを削除する。
def unlike(post)
likes_posts.destroy(post)
end
#likes_postsテーブルに引数のpostオブジェクトに該当するレコードがあるかを判断する。
def like?(post)
likes_posts.include?(post)
end
def full_name
"#{last_name} #{first_name}"
end
end
likes_posts << postのところでcreate!メソッドを使わないのは、create!メソッドを使ってしまうとlikesテーブルにレコードが追加されずにlikeが生成されてしまうからみたいです(詳しくはよく分かりません、、)。
これでモデルの編集は終わりました。
②コントローラーの作成
$ rails g controller Likes create destroy
createメソッドとdestroyメソッドだけ追加します。この時、ビューでcreate.html.erbとdestroy.html.erbも生成されますが不要なので削除しておきます。
次はlikes.controller.rbの編集です。
likes.controller.rb
class LikesController < ApplicationController
def create
post = Post.find(params[:post_id])
current_user.like(post)
redirect_back fallback_location: posts_path
end
def destroy
post = current_user.likes_posts.find(params[:post_id])
current_user.unlike(post)
redirect_back fallback_location: posts_path
end
end
createメソッドとdestroyメソッドでは、それぞれモデルで設定したlikeメソッドとunlikeメソッドを使用しています。redirect_backは直前のページにリダイレクトするメソッドです。fallback_locationオプションを設定することで、直前のページが見つからない場合のリダイレクト先を指定できます。
③ルーティングの設定
routes.rb
Rails.application.routes.draw do
get 'login', to: 'user_sessions#new'
post 'login', to: 'user_sessions#create'
delete 'logout', to: 'user_sessions#destroy'
resources :posts do
resources :likes, only: %i[create destroy]
collection do
get :likes
end
end
resources :users, only: %i[new create]
end
collectionを使うことで/posts/likesのURLを生成することができます。
posts_controller.rb
def likes
@posts = current_user.likes_posts
end
posts#likesのルーティングを設定したので、likesメソッドを設定します。@postsにcurrent_userがlikeした投稿のオブジェクトを入れるようにします。これでlikeしたpostの一覧を表示するためのアクションを設定できました。
④Viewの編集・作成
最後はviewの編集と作成です。まずはそれぞれのpostにlikeボタンを表示させる設定をしていきます。
/posts/index.html.erb
<省略>
<table>
<thead>
<tr>
<th>Title</th>
<th>Content</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @posts.each do |post| %>
<tr>
<td><%= post.title %></td>
<td><%= post.content %></td>
<td><%= link_to 'Show', post %></td>
<% if current_user.mine?(post) %>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% else %>
#ボタンのパーシャルを呼び出す
<%= render 'like_button', {post: post} %>
<% end %>
</tr>
<% end %>
</tbody>
</table>
Likeボタンはパーシャルに記載します。パーシャルにはpostにpostオブジェクトを入れて渡すように設定します。
posts/_like_buttun.html.erb
#userモデルの設定したlike?メソッドを使って、postオブジェクトに対してcurrent_userがlikeしているかを確認。
<% if current_user.like?(post) %>
<%= render 'unlike', { post: post } %>
<% else %>
<%= render 'like', { post: post } %>
<% end %>
まずcurrent_user.like?(post)で引数に渡されたpostオブジェクトにcurrent_userがlikeしているかどうかを確認します。likeしていればunlikeパーシャルを呼び出し、likeしてなければlikeパーシャルを呼び出します。
/posts/_like.html.erb
<td><%= link_to 'Like', post_likes_path(post_id: post.id), method: :post %></td>
/posts/_unlike.html.erb
<td><%= link_to 'UnLike', post_like_path(post_id: post.id, id: current_user.likes.find_by(post_id: post.id)), method: :delete %></td>
likeとunlikeのパーシャルを作成します。ここは、もっとシンプルに書けるのかもしれません。
/posts/likes.html.erb
<h1>Liked Posts</h1>
<table>
<thead>
<tr>
<th> Auther</th>>
<th>Title</th>
<th>Content</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @posts.each do |post| %>
<tr>
<td><%= post.user.full_name %></td>
<td><%= post.title %></td>
<td><%= post.content %></td>
<td><%= link_to 'Show', post %></td>
</tr>
<% end %>
</tbody>
</table>
<%= link_to 'Back', posts_path %>
最後にlike一覧のビューを作成します。full_nameはuserの性と名を連結させたメソッドです。userモデルに定義してあるメソッドになります。
user.rb
def full_name
"#{last_name} #{first_name}"
end
これでlike機能が使えるようになりました。まだまだ冗長なコードになってる部分もあると思うので課題はありそうです。最後まで読んでくださりありがとうございました〜。少しでも参考になれば幸いです!!