LoginSignup
3
4

More than 3 years have passed since last update.

【Rails】いいね機能を非同期実装

Last updated at Posted at 2020-08-11

1. はじめに

以下のデモ動画の様に、ユーザーが投稿した内容に対して"いいね"が出来る機能を実装していきます。
Image from Gyazo

2. 前提条件

既にユーザー登録機能と投稿機能は実装されている前提で、そこに"いいね機能"を追加実装する。という流れで進めていきます。

下記の様なデータベース構造をイメージしてもらえたら分かりやすいと思います。
ER 図(Qiita).png

3. いいね機能の実装

■実装するまでの流れ

ざっくり説明すると、以下の流れで実装していきます。
・モデルの作成
   ↓
・ルーティングの追加
   ↓
・コントローラーの作成
   ↓
・ビューの作成

それでは、早速いってみましょー。

3-1. Likeモデルの作成

まずはLikeモデルを作成します。
ターミナルで以下のコマンドを実行してください。

ターミナル
$ rails g model Like

新しくマイグレーションファイルが作成されるので、以下の通りに編集してください。

db>migrate>xxxxxx_create_likes.rb
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_iduser_idを外部キーとして指定することが出来ます。
それではマイグレーションファイルを実行して、likesテーブルを作成しましょう。

ターミナル
$ rails db:migrate

上記のコマンドを実行した後、likesテーブルが作成されたかどうか確認して下さい。
無事作成されている事が確認出来たら、次はアソシエーションの設定です。

3-2. アソシエーションの設定

アソシエーションとは、2つのモデル同士の関連付けのことを指します。
UserモデルとLikeモデル、TweetモデルとLikeモデル、それぞれのアソシエーションを設定していきます。

UserモデルとLikeモデルのアソシエーションの設定

まずはUserモデルとLikeモデルのアソシエーションを設定していきます。

2つのモデルの関係性は以下の通りです。
・ ユーザーは複数のいいねが可能
・ いいねAをしたユーザーは1人しかいない

つまり、UserモデルとLikeモデルは「1対多」の関係になります。
それでは、実際にコードを書いていきましょう。

Userモデルに以下の通りコードを追記して下さい。

app>models>user.rb
class User < ApplicationRecord

  has_many :tweets, dependent: :destroy

  # この行を追加
  has_many :likes

end

has_many は、他のモデルとの間に「1対多」の関係があることを示します。

次はLikeモデルに以下の通りコードを追記して下さい。

app>models>like.rb
class Like < ApplicationRecord

  # この行を追加
  belongs_to :user

end

belongs_tohas_many の逆で、他のモデルとの間に「多対1」の関係があることを示しています。

これで、UserモデルとLikeモデルのアソシエーション設定が出来ました。

TweetモデルとLikeモデルのアソシエーションの設定

同じ要領でTweetモデルとLikeモデルのアソシエーションも設定していきます。

2つのモデルの関係性は以下の通りです。
・ 1つの投稿に対して、複数のいいねがつく
・ いいねAに紐づく投稿は1つしかない

つまり、TweetモデルとLikeモデルも「1対多」の関係になります。
それでは、実際にコードを書いていきましょう。

Tweetモデルに以下の通りコードを追記して下さい。

app>models>tweet.rb
class Tweet < ApplicationRecord
  belongs_to :user

  # この行を追加
  has_many :likes, dependent: :destroy

end

dependent: :destroy をつける事で、投稿が削除された時に、その投稿に紐づくいいねも削除されます。

次はLikeモデルです。

app>models>like.rb
class Like < ApplicationRecord

  belongs_to :user

  # この行を追加
  belongs_to :tweet

end

以上で、全てのモデルのアソシエーションの設定が完了しました。

3-3. バリデーションの設定

投稿Aに対して1人のユーザーがいいねを押せる回数は1回にしたいので、1回以上は押せない様にバリデーションを設定します。

Likeモデルに以下の通り追記して下さい。

app>models>like.rb
class Like < ApplicationRecord

  belongs_to :user
  belongs_to :tweet

  # この行を追加
  validates :user_id, uniqueness: { scope: :tweet_id }

end

上記の様に書く事で、user_idtweet_id が重複しない様にする事が出来ます。
以上で、バリデーションの設定は完了です。

3-4. ルーティングの追加

いよいよ本格的にいいね機能を実装していきます。

まずは、いいね機能で使うルーティングを追加しましょう。
以下の通りコードを追記して下さい。

config>routes.rb
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 アクションを作成していきます。
以下の通りコードを追記して下さい。

app>controllers>likes_controller.rb
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モデルに以下の通りコードを追記して下さい。

app>models>tweet.rb
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_idtweet_id が一致するlikeを探して、無ければnillを返します。

それでは、app/views/tweets/index.html.erb に以下のコードを追記して下さい。

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_toremote: true オプションを追加することを忘れないでください。

なお"いいねボタン"のアイコンについては、Font Awesome を利用しています。
導入方法については、以下のqiita記事などが参考になるかと思います。
rails font-awesome-sass導入方法

次は、createアクションが実行された時に出力するファイルを作成します。
app/views フォルダ直下にlikes フォルダを作成し、その中にcreate.js.erb ファイルを作成してください。

ファイルの作成ができたら、以下の通りコードを追記してください。

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 ファイルを作成し、以下のコードを追加してください。

app>views>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 ファイルを作成してください。

ファイルの作成ができたら、以下の通りコードを追記してください。

app>views>likes>destroy.js.erb

$('#<%= @tweet.id.to_s %>').
  html('<%= j render "tweets/like", { tweet: @tweet } %>');

tweets フォルダの中に_like.html.erb ファイルを作成し、以下のコードを追加してください。

app>views>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で自分好みにカスタマイズしてみてください。

私の場合は以下の様にして、いいねがされた時はレッド、されていない時はグレーにアイコンの色を変えています。

app>assets>stylesheets>tweets>_tweet.css
.like {
  color: gray;
}

.liked {
  color: red;
}

4. さいごに

今回がはじめての投稿になりますが、記事を書くのって想像していたよりも大変ですね。
もし分かりにくい部分や、間違っている部分がある場合はご指摘いただけると嬉しいです。

最後まで読んでいただき、ありがとうございました☺️

3
4
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
3
4