1. はじめに
以下のデモ動画の様に、ユーザーが投稿した内容に対して"いいね"が出来る機能を実装していきます。
2. 前提条件
既にユーザー登録機能と投稿機能は実装されている前提で、そこに"いいね機能"を追加実装する。という流れで進めていきます。
下記の様なデータベース構造をイメージしてもらえたら分かりやすいと思います。
3. いいね機能の実装
■実装するまでの流れ
ざっくり説明すると、以下の流れで実装していきます。
・モデルの作成
↓
・ルーティングの追加
↓
・コントローラーの作成
↓
・ビューの作成
それでは、早速いってみましょー。
3-1. Likeモデルの作成
まずはLikeモデルを作成します。
ターミナルで以下のコマンドを実行してください。
$ rails g model Like
新しくマイグレーションファイルが作成されるので、以下の通りに編集してください。
class CreateLikes < ActiveRecord::Migration[6.0]
def change
create_table :likes do |t|
# ===追記部分===
t.references :tweet, foreign_key: true, null: false
t.references :user, foreign_key: true, null: false
# ===追記部分===
t.timestamps
end
end
end
上記の様にreferences型で保存すると、tweet_id
とuser_id
を外部キーとして指定することが出来ます。
それではマイグレーションファイルを実行して、likesテーブルを作成しましょう。
$ rails db:migrate
上記のコマンドを実行した後、likesテーブルが作成されたかどうか確認して下さい。
無事作成されている事が確認出来たら、次はアソシエーションの設定です。
3-2. アソシエーションの設定
アソシエーションとは、2つのモデル同士の関連付けのことを指します。
UserモデルとLikeモデル、TweetモデルとLikeモデル、それぞれのアソシエーションを設定していきます。
UserモデルとLikeモデルのアソシエーションの設定
まずはUserモデルとLikeモデルのアソシエーションを設定していきます。
2つのモデルの関係性は以下の通りです。
・ ユーザーは複数のいいねが可能
・ いいねAをしたユーザーは1人しかいない
つまり、UserモデルとLikeモデルは「1対多」の関係になります。
それでは、実際にコードを書いていきましょう。
Userモデルに以下の通りコードを追記して下さい。
class User < ApplicationRecord
has_many :tweets, dependent: :destroy
# この行を追加
has_many :likes
end
has_many
は、他のモデルとの間に「1対多」の関係があることを示します。
次はLikeモデルに以下の通りコードを追記して下さい。
class Like < ApplicationRecord
# この行を追加
belongs_to :user
end
belongs_to
はhas_many
の逆で、他のモデルとの間に「多対1」の関係があることを示しています。
これで、UserモデルとLikeモデルのアソシエーション設定が出来ました。
TweetモデルとLikeモデルのアソシエーションの設定
同じ要領でTweetモデルとLikeモデルのアソシエーションも設定していきます。
2つのモデルの関係性は以下の通りです。
・ 1つの投稿に対して、複数のいいねがつく
・ いいねAに紐づく投稿は1つしかない
つまり、TweetモデルとLikeモデルも「1対多」の関係になります。
それでは、実際にコードを書いていきましょう。
Tweetモデルに以下の通りコードを追記して下さい。
class Tweet < ApplicationRecord
belongs_to :user
# この行を追加
has_many :likes, dependent: :destroy
end
dependent: :destroy
をつける事で、投稿が削除された時に、その投稿に紐づくいいねも削除されます。
次はLikeモデルです。
class Like < ApplicationRecord
belongs_to :user
# この行を追加
belongs_to :tweet
end
以上で、全てのモデルのアソシエーションの設定が完了しました。
3-3. バリデーションの設定
投稿Aに対して1人のユーザーがいいねを押せる回数は1回にしたいので、1回以上は押せない様にバリデーションを設定します。
Likeモデルに以下の通り追記して下さい。
class Like < ApplicationRecord
belongs_to :user
belongs_to :tweet
# この行を追加
validates :user_id, uniqueness: { scope: :tweet_id }
end
上記の様に書く事で、user_id
とtweet_id
が重複しない様にする事が出来ます。
以上で、バリデーションの設定は完了です。
3-4. ルーティングの追加
いよいよ本格的にいいね機能を実装していきます。
まずは、いいね機能で使うルーティングを追加しましょう。
以下の通りコードを追記して下さい。
Rails.application.routes.draw do
devise_for :users,
controllers: { registrations: 'registrations' }
resources :tweets, only: [:index, :new, :create, :show, :destroy] do
# この行を追加
resources :likes, only: [:create, :destroy]
end
end
いいね情報の保存と削除のルーティングを追加する必要があるので、likesコントローラーのcreate
アクションとdestroy
アクションを定義しています。
ルーティングをネストにする事で、いいねがどの投稿に紐づくかを明示できます。
コードを追加したらrails routes
コマンドで、ルーティングの設定が問題ないか忘れずに確認しておきましょう。
3-5. likesコントローラーの作成
次にlikesコントローラーを作成していきます。
ターミナルで以下のコマンドを実行してください。
$ rails g controller likes
上記のコマンドを実行すると、likesコントローラーが作成できます。
それでは作成したlikesコントローラーにcreate
アクションとdestroy
アクションを作成していきます。
以下の通りコードを追記して下さい。
class LikesController < ApplicationController
# ===追記部分===
def create
@like = current_user.likes.build(like_params)
@tweet = @like.tweet
if @like.save
respond_to :js
end
end
def destroy
@like = Like.find_by(id: params[:id])
@tweet = @like.tweet
if @like.destroy
respond_to :js
end
end
private
def like_params
params.permit(:tweet_id)
end
# ===追記部分===
end
privateメソッドやparamsは理解できているものとして、追加したコードについて簡単に説明していきます。
createアクション
まず@like
には投稿に"いいね"をしたユーザーのuser_id
と、"いいね"された投稿のtweet_id
の情報が入っています。
このコードはbuildメソッドを使って、インスタンスを作成しています。
次に@tweet
には@like
に紐づく投稿の情報、つまり"いいね"された投稿の情報が入ります。
@tweet
はどの投稿に"いいね"を押したのかを判断するために、ビューを作成するところで使います。
最後のif @like.save
の部分は、"いいね"が押された時に返すレスポンスのフォーマットをrespond_to
メソッドで切り替えています。
"いいね"が押されたらリアルタイムでビューを反映させるために、JS形式のフォーマットでレスポンスを返すようにしています。
destroyアクション
createアクションのところで説明した内容と重複している部分が多いので簡単に説明すると、受け取ったHTTPリクエストからid
を判別し、@like
に指定のレコードの情報を入れています。
こちらもリアルタイムでビューを反映させるために、JS形式のフォーマットでレスポンスを返すようにしています。
3-5. ビューの作成
いよいよビューの作成です。
まずは投稿一覧のビュー画面を編集していきましょう・・・と言いたいところですが、ビューで使うためのメソッドを先に定義しておきます。
Tweetモデルに以下の通りコードを追記して下さい。
class Tweet < ApplicationRecord
belongs_to :user
has_many :likes, dependent: :destroy
# 追加部分(liked_byメソッド)
def liked_by(user)
Like.find_by(user_id: user.id, tweet_id: id)
end
# 追加部分
end
上記で追加したliked_by
メソッドは、user_id
とtweet_id
が一致するlikeを探して、無ければnillを返します。
それでは、app/views/tweets/index.html.erb
に以下のコードを追記して下さい。
<% @tweets.each do |tweet| %>
# いいねボタンを表示したい部分に追加
<div class="content-like">
<ul class="content-like__icons">
<li id="<%= tweet.id.to_s %>">
<% if tweet.liked_by(current_user).present? %>
<%= link_to (tweet_like_path(tweet.id, tweet.liked_by(current_user)), method: :DELETE, remote: true, class: "liked") do %>
<i class="far fa-thumbs-up"></i>
<% end %>
<% else %>
<%= link_to (tweet_likes_path(tweet), method: :POST, remote: true, class: "like") do %>
<i class="far fa-thumbs-up"></i>
<% end %>
<% end %>
</li>
</ul>
</div>
# 追加部分はここまで
<% end %>
liked_by
に引数としてcurrent_user
を渡すことで、現在ログインしているユーザーが投稿に"いいね"をしているかどうか判断しています。
これでユーザーが"いいね"をしていない時に"いいねボタン"をクリックすると、先ほど作成したcreate
アクションを実行、ユーザーが"いいね"をしている時はdestroy
アクションを実行と、条件分岐させる事ができました。
リンクが押された時に.js.erb
ファイルを呼び出す必要があるので、link_to
にremote: true
オプションを追加することを忘れないでください。
なお"いいねボタン"のアイコンについては、Font Awesome を利用しています。
導入方法については、以下のqiita記事などが参考になるかと思います。
rails font-awesome-sass導入方法
次は、createアクションが実行された時に出力するファイルを作成します。
app/views
フォルダ直下にlikes
フォルダを作成し、その中にcreate.js.erb
ファイルを作成してください。
ファイルの作成ができたら、以下の通りコードを追記してください。
$('#<%= @tweet.id.to_s %>').
html('<%= j render "tweets/liked", { tweet: @tweet } %>');
上記のコードで、createアクションが実行されたらtweets
フォルダ内の_liked.html.erb
ファイルを呼び出しています。
tweets
フォルダの中に_liked.html.erb
ファイルを作成し、以下のコードを追加してください。
<%= link_to (tweet_like_path(tweet.id, tweet.liked_by(current_user)), method: :DELETE, remote: true, class: "liked") do %>
<i class="far fa-thumbs-up"></i>
<% end %>
上記のコードで、"いいねボタン"を押したら"いいね"を取り消すHTMLを表示するようにしています。
同じ流れで、destroyアクションが実行された時に呼び出されるファイルも作っていきましょう。
app/views>likes
フォルダの中にdestroy.js.erb
ファイルを作成してください。
ファイルの作成ができたら、以下の通りコードを追記してください。
$('#<%= @tweet.id.to_s %>').
html('<%= j render "tweets/like", { tweet: @tweet } %>');
tweets
フォルダの中に_like.html.erb
ファイルを作成し、以下のコードを追加してください。
<%= link_to (tweet_likes_path(tweet), method: :POST, remote: true, class: "like") do %>
<i class="far fa-thumbs-up"></i>
<% end %>
以上で非同期でいいね機能を実装する事ができました。
あとは見た目ですが、クラス名を、いいねされている時はliked
されていない時はlike
としていますので、CSSで自分好みにカスタマイズしてみてください。
私の場合は以下の様にして、いいねがされた時はレッド、されていない時はグレーにアイコンの色を変えています。
.like {
color: gray;
}
.liked {
color: red;
}
4. さいごに
今回がはじめての投稿になりますが、記事を書くのって想像していたよりも大変ですね。
もし分かりにくい部分や、間違っている部分がある場合はご指摘いただけると嬉しいです。
最後まで読んでいただき、ありがとうございました☺️