非同期通信でいいね!機能を作る

TECH::CAMPで勉強を始めて14週目。

気がつくと、卒業まであと100日を切っております。

早いもんで・・

 

勉強時間に比例して知識が身についているかどうか不安ではあるのですが、

Web上に投稿されているプログラミングに関する記事の理解度に関しては確実に上がっています。

→インプットする力は上がっている

大事なのは、インプットしたものを自分の物にしてアウトプットできるかどうか。

これは数をこなすしかないと思うので、

色んな知識を身につけながらとにかく手を動かして、考えていくしかないかなと!

 

今回は自作アプリにいいね!機能をつけたときの記録。

自分の理想としては、

・非同期通信にしたい

・自分がいいね!したものは赤く塗り潰したい

・いいね!の数を表示したい

ということを掲げて色々調べながら実装しました。

 

✴︎目次✴︎

  

 

ルーティング、Likeモデルを作る

ルーティングは下記の通り。

post(投稿)にネストさせ、どの記事に対するlikeなのかわかるようにしておきます。

機能はいいね!をつける・いいね!を外すという2つのみなので

createとdestroyだけでOK

resources :posts, except: :index do
resources :likes, only: [:create, :destroy]
end

 

Likeモデルを作成し、

バリデーションで、1つの投稿には一人一回しかいいね!できないことにします。

(いいね!をつけたり外したりは可能)

また、postモデルとuserモデルとのアソシエーションをつけておきます。

class Like < ApplicationRecord
belongs_to :post
belongs_to :user
 
validates_uniqueness_of :post_id, scope: :user_id
 
end

 

Likesテーブルでは、

どの投稿に対し、誰がいいね!したのかわかるように

user_idとpost_idを取得します。

テーブルの関係図はこんな感じです。

(commentsテーブルは今回は無視してください)

f:id:atsukofu:20200525213344p:plain

 

これでuser,like,post間の紐付けができました。

 

 

コントローラーとアクションを実装する

先ほどルーティングに記載しておいた

createとdestroyアクションに対して機能を実装します。

likes_controller.rb

class LikesController < ApplicationController
before_action :set_post
 
def create
@like = current_user.likes.new(post_id: @post.id)
@like.save
@likes = Like.where(post_id: @post.id)
end

def destroy
@like = Like.find_by(post_id: @post.id, user_id: current_user.id).destroy
@likes = Like.where(post_id: @post.id)
end

private
def set_post
@post = Post.find(params[:post_id])
end
end

set_postメソッドで、ルーティングの親であるpostのidを@postとして

使えるようにしています。

また、いいね!をつけた時、解除した時両方で

その記事のいいね!の数を取得するようにしています。

 

 

 

ビューの作成

今回はfont-awesomeで枠線だけのハートと、塗り潰しハートを使用します。

枠線だけのハートは、自分がいいね!していない記事に表示されます。

塗り潰しハート(今回はCSSで赤く色をつけました)は、

自分がいいね!している記事に表示されます。

イメージはこちら

・自分が投稿記事に対しいいね!していないとき

 f:id:atsukofu:20200602212019p:plain

・自分が投稿記事に対しいいね!しているとき

 f:id:atsukofu:20200602211911p:plain

 

 

これは投稿詳細ページ中のいいね!部分。↓

<div id="like-<%= @post.id %>">
<%= render 'likes/like', { post: @post} %>
</div>

idを、”like-<%= @post.id %>"とすることで、

投稿のid名がついたid名をつけることができます。

このidは、jsで操作する際に使用します。

 

また、renderを使用し、likeディレクトリの_like.html.erbというファイルを

参照しています。(※部分テンプレートなのかは後でわかります)

次はそちらのファイルをみてみましょう。

 

likes/_like.html.erb

<%if user_signed_in? %>
<% if Like.find_by(user_id: current_user.id, post_id: post.id) %>
<%=link_to post_like_path(post, post.likes),
{method: "delete", remote: true, class: "likes-count"} do %>
<i class="fas fa-heart haert-red"></i>
<%= post.likes.count %>
<% end %>
<% else %>
<%=link_to post_likes_path(post),
{method: "post", remote: true, class: "likes-count"} do %>
<i class="far fa-heart"></i>
<%= post.likes.count %>
<% end %>
<% end %>
<% else %>
<i class="far fa-heart"></i><%= post.likes.count %>
<% end %>

まず、冒頭のif user_signed_in?は、

railsで使える、ユーザーがログインしているかどうか?というのを判定するメソッドです。

今回は、ログインしていれば、いいね!をつけたり外したりできるけども

ログインしていない場合は、

いいね!の数をみるだけしかできないという仕様にしています。

 

また、link_toで呼び出すアクションを指定していますが

自分がいいね!している記事だとdestroyアクション、

まだ自分がいいね!していない記事だとcreateアクションが呼び出されます。

 

link_toタグの中の「remote: true」という記述ですが、

 これを書いておくことで、操作をjavascriptに飛ばせるという仕組みだそうです。

まだ完全に理解はできておりませんが、仕組み自体はそんな感じ。

 

 

jsファイルを記述する

では、jsファイルの記述です。

こちらもlikesディレクトリに保存しています。

今回は、「ファイル名.js.erb」というファイル形式で作っていきます。

このファイル形式、私は初めて見ました。

このファイル形式だと、jsの式の中に、erbタグを使用することで、

Rubyのコードがかけちゃう、ハイブリッド的?!なもののようです。

例えば、コントローラーで定義したインスタンスをerbタグ内に書くことで

このファイル内でも使用することができるのです。

 

ファイル名は、create.js.erbとdestroy.js.erbとし、

両方とも記述内容は同じです。

$("#like-<%= @post.id %>")
.html("<%= j(render partial: 'like', locals: { post: @post }) %>");

(横幅入りきらないので、途中で改行しちゃってますが、1行での記述です。)

 

ここで、先ほど投稿詳細ページに記載した

<div id="like-<%= @post.id %>">

というid名が生きてくるわけです。

上記のjsではid名で操作するビューを取得していますが、

このid名が、先ほどビューに入れた不思議なid名を取得しているのです。

例えば、投稿のidが「3」だったとすると

id名は「like-3」で、jsでは「#like-3」を取得していることになります。

 

取得したidに対し、htmlの書き換えを行います。

html( )メソッドを使用し、カッコ内の部分を非同期で書き換えますよ〜ということです。

カッコ内の部分はというと、先ほど見ていただきました

likes/_like.html.erb のファイルです。

link_toでアクション呼び出し→コントローラーで処理→モデルにてデータを登録

                 ↓

             ビューはjsで部分的に書き換え

という感じですね!!!

 

※ちなみにrenderの前に記載の「j」は特殊文字エスケープするためのメソッドです。

 

 

今回の感想と今後やりたいこと

たくさんのWeb上の記事を拝見しまして、

結構時間がかかって実装したこのいいね!機能ですが、

後からまとめてみると、id名の部分ですとか、なぜ部分テンプレートにするのかとか、

色んな伏線を回収したような気になって、

理解できた時はとてもうれしかったです。

 

今後のいいね!機能に対する目標

ajaxについての知識を固めて、説明できるレベルになりたい

・いいねの数をいいねした時・解除したときだけではなく、

 何秒か起きに非同期で取得したい

 (ツイッターとかリアルタイムでいいね数増えますよね。

  臨場感あっていいですよね。)

 

色んな記事を参考にして得た私の頭の中を公開しましたが、

まだまだ初学者ですので、わかりにくい部分や間違いがあれば、

ご指摘いただけますと嬉しい限りです。

 

長い記事を読んでいただきありがとうございます!