rails いいね機能を実装してみる
いいね機能を実装してみようと思います!
前提として...
・userテーブルとarticleテーブルは作成済み
・user : article = 1 : Nの関係
・簡単な記事投稿機能も作成済み
いいねテーブル(中間テーブル)の作成
$ rails g model Like user:references article:references
次に、作られたマイグレーションファイルに追記します
class CreateLikes < ActiveRecord::Migration[6.1]
def change
create_table :likes do |t|
t.references :user, null: false, foreign_key: true
t.references :article, null: false, foreign_key: true
t.timestamps
t.index [:user_id, :article_id], unique: true 👈 追記
end
end
end
※ foreign_key:true は、すでにあるuser_idやpost_idからしか登録できないということ。
※ t.index [:user_id, :post_id], unique: true を記述することで、user_idとarticle_idのペアは一つしか作ることができないという決まりを設けらる。
モデル
user.rb
has_many :likes
has_many :like_articles, through: :likes, source: :article
※上記の through: :likes は、user => likes => articleの流れでデータを取得できるようにしている。
※ source: :articleがないと、railsがuser => like => ? となる為、railsにどのテーブルを参照するのかを記述している。(私はこれがなくてエラーが出ました。笑)
article.rb
has_many :likes
has_many :users, through: :likes
※ articleテーブルは、article => user 流れがすでにbelongs_to :userで定義されているので、sourceオプションをつけなくても、 article => like => user の流れをrailsが汲み取ってくれる。
like.rb
belongs_to :user
belongs_to :article
コントローラー
$ rails g controller likes create destroy
として、
likes_controller.rb
class LikesController < ApplicationController
before_action :set_user_and_article
def create
Like.create(user_id: @user.id, article_id: @article.id)
redirect_to article_path(@article)
end
def destroy
like = Like.find_by(user_id: @user.id, article_id: @article.id)
like.delete
redirect_to article_path(@article)
end
private
def set_user_and_article
@user = current_user
@article = Article.find(params[:id])
end
end
こんな感じです。
createの流れとしては、before_actionで、インスタンス変数にuserとarticleの情報を入れて、likeにそれぞれのidを渡しているということです!
destroyの流れとしては、createと同じくbefore_actionの後に、現在のユーザーと、記事のページが持つid(現在の記事ページのこと)に合致するidを持つlikeをfind_byで探してきて、deleteしています!
ビュー
ビューに関しては、articleのshowページに実装するので、rails g する必要はないです。(私はコントローラー作成の時に勝手にできたので消しました。💪)
なので、専用のビューの代わりに部分テンプレートとして、いいねを作ります。
article/show.html.erb
<div>
<%= render 'like', article: @article %>
</div>
これを、いいねを表示したいところに記述するだけです。
article: @article は、likeコントローラーで定義した@articleを部分テンプレートの中でarticleとして使うようにする為の記述です。
_like.html.erb
<% if article.liked_by?(current_user) %>
<%= link_to(article_unlike_path(article), method: :delete) do %>
<p>いいね解除</p>
<% end %>
<% else %>
<%= link_to(article_like_path(article), method: :post) do %>
<p>いいね</p>
<% end %>
<% end %>
article.liked_by?(current_user)で、現在のユーザーがいいねをしているかどうかを、if文で確かめています。
liked_by?というメソッドはこんな感じで定義しています。
article.rb
def liked_by?(user)
likes.where(user_id: user.id).exists?
end
(user)は引数で、実際にはcurrent_userが入ります。
.whereでlikeテーブルのuser_idと現在のユーザーのidが同じものを探して、exisits?をつけることで、結果をtrue or falseで返すようにしています。
なのでいいねがあればtrue、なければfalseですね。
今回は、いいねボタンというよりは、いいねテキストなので、この辺はちゃんといいねボタンを追々作ろうと思っています!
ルーティング
resources :articles do
post 'like/:id' => 'likes#create', as: 'like'
delete 'like/:id' => 'likes#destroy', as: 'unlike'
end
ルーティングに関しては、自分でもはっきりとこれが良いみたいなのが出てないので、とりあえず動く感じで。
一応、articleからネストしてみました。
いいねの数を表示する
<% @articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= article.likes.count %></td> 👈 これ!
<td><%= link_to 'Show', article %></td>
</tr>
<% end %>
.countメソッドで簡単に数を表示できます。便利!
自分がいいねした記事だけを表示する
ここでは、先ほどuser.rbで定義した
user.rb
has_many :like_articles, through: :likes, source: :article
これを使います。
ここでは、user => like => articleの流れを持つ、like_articlesという名前をつけています。
なので、@user.like_articlesとすることで@userのlikeに紐づくarticlesを表示することができます。
コントローラー
users_controller.rb
def liked_articles
user = User.find_by(id: params[:id])
@liked_articles = user.like_articles
end
ルート
get "/users/:id/liked_articles", to: "users#liked_articles", as: 'users_liked_articles'
ビュー
<% @liked_articles.each do |article| %>
<tr>
<td><%= article.title %></td>
<td><%= link_to 'Show', article %></td>
</tr>
<% end %>
こんな感じです!
これで、ユーザーは自分がいいねした記事を見ることができるようになりました。
まとめ
ざっくりまとめてみました!
いいね機能に関しての処理には非同期を用いることが必須だと思っているので、非同期で処理ができるようになりたいですね!