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 %>


こんな感じです!

これで、ユーザーは自分がいいねした記事を見ることができるようになりました。


まとめ

ざっくりまとめてみました!

いいね機能に関しての処理には非同期を用いることが必須だと思っているので、非同期で処理ができるようになりたいですね!

いいなと思ったら応援しよう!