目標

Twitter様よろしくいいね機能を今回も機能実装から行っていく。
Progate様のRailsレッスンを参考にしています。

モデル生成

$ rails g model Like user_id:references micropost_id:references

今回はログイン済みユーザー(user_idをもつユーザー)でないとコメントできないようにするためにreferencesでモデルを生成した。
モデルには関連付けとバリデーションも設けた。

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :micropost
  validates :user_id , {presence: true}
  validates :micropost_id , {presence: true}
end

バリデーションは、Likeモデルは両方が存在しなければ不完全なデータであるとの考えから。

ルーティング



  post "likes/:micropost_id/create" , to: 'likes#create'
  delete 'likes/:micropost_id/destroy' , to: 'likes#destroy'


後ほどいいねキャンセルも作るのでdestroyも一緒に。
コメント機能同様ネストしてから、HTTPメソッドをresoucesでonlyオプションでやれば一行でまとまるのかな?と考えたりしましたがリファクタリングは後程。

コントローラ

class LikesController < ApplicationController

    def create
        @like = Like.create(
            user_id: current_user.id ,
            micropost_id: params[:micropost_id])

            redirect_to request.referrer || root_url
    end

    def destroy 
        @like = Like.find_by(
            user_id: current_user.id ,
            micropost_id: params[:micropost_id])
        @like.destroy 
        redirect_to  request.referrer || root_url
    end


end

ここに関してはパラメータの引数をしっかりmicropost_idと明示することが注意すべき点でしょうか。そうでもないでしょうか。

この時点で一回コンソールでLike.createしてみました。(無事生成され、データベースにも保存できています)

ビュー

いいね実装で一番頭を使ったところでした。

簡単に考えて、
if分岐で
 いいね済みならdestroyリンク
まだなら
 createリンク

更に自分のマイクロポストにはいいね出来ず、いいねの数を表示したい

更に更に今回はprogate様のを丸々参考してFontAwesomeを導入して”いいね”の文字をハートで表したい

出来上がったのがこちら↓

  <% if micropost.user_id == current_user.id %> <!-- 自分の投稿にはいいねcreate対象外で、いいねされた数を出力する-->
        <span class="fa fa-heart" aria-hidden="true"></span>
        <span><%= Like.where(micropost_id: micropost.id).count %>
    <% else %>
        <% if Like.find_by(user_id: current_user.id , micropost_id: micropost.id) %> <!-- 自分以外の投稿に対するif文内にて、特定のmicropostにいいねをしていたらdestroyへ。の条件分岐-->
            <%= link_to("いいね済み" , "/likes/#{micropost.id}/destroy" , {method: "delete"})%>

        <% else %>
              <%=link_to("/likes/#{micropost.id}/create" , {method: "post"}) do%>
              <span class="fa fa-heart" aria-hidden="true"></span>
          <% end %>
        <% end %>
  <% end %>

FontAwesomeの導入

1、application.htmlのheadタグに

を張付け

2、FAはクラス名に使いたいマークの名前を与える事でビューに表示できる仕組み(link_toの引数として使いたい場合は例外)
例)

→♡  /ビュー

今回は例外の部分に当たるわけで、多少コードが変わります
例)<%= link_to ("URL") do%>
<!-- ブロック内でHTMLのタグ(withクラス名fa 〇〇)をここに記述-->
  <% end %>

<%=link_to("/likes/#{micropost.id}/create" , {method: "post"}) do%>
   <span class="fa fa-heart" aria-hidden="true"></span>
<% end %>

今回の僕の場合は上の様になりました。

ここまでで、コメント機能、いいね機能をあわせて実装したフィード用マイクロポストパーシャルを乗っけておきます

/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
      <%= link_to micropost.content , micropost %>
      <%= image_tag micropost.picture.url if micropost.picture?%>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
      <% if current_user?(micropost.user) %>
            <%= link_to"delete" , micropost, method: :delete, data: {confirm: "You sure?"}%>
      <% end %>
  </span>

  <% if micropost.user_id == current_user.id %> <!-- 自分の投稿にはいいねcreate対象外で、いいねされた数を出力する-->
        <span class="fa fa-heart" aria-hidden="true"></span>
        <span><%= Like.where(micropost_id: micropost.id).count %>
    <% else %>
        <% if Like.find_by(user_id: current_user.id , micropost_id: micropost.id) %> <!-- 自分以外の投稿に対するif文内にて、特定のmicropostにいいねをしていたらdestroyへ。の条件分岐-->
            <%= link_to("いいね済み" , "/likes/#{micropost.id}/destroy" , {method: "delete"})%>

        <% else %>
              <%=link_to("/likes/#{micropost.id}/create" , {method: "post"}) do%>
              <span class="fa fa-heart" aria-hidden="true"></span>
              <% end %>
        <% end %>
  <% end %>
  <span><%= "コメント(#{micropost.comments.count})"%></span>
</li>

動かしてみる(問題発生)

早速ブラウザで動くか試してみた。
ランダムにいくつかのマイクロポストにいいねを乱射してみた

♡ <-> いいね済み

が いったりきたり。
よしよし。と思っていると一つ、

いいね済み→いいね済み

もう一度押しても同じ動き。
4度目で♡になった。

原因

ブラウザで動作確認をする時ログインするのは必ずと言っていいほどuser_id=1のユーザーだ。
そしていじるマイクロポストはフィードの一番上にあるマイクロポスト(micropost_id:300)。

コンソールでコメント、いいねを生成する時も、Micropost.first で引っ張って(micopost_id:300) 、 user_id=1で作っている。
(なのでそのマイクロポストだけコメント(15)になっていたりする)

今回いいねを3回押してやっと♡になったのは確認作業でブラウザ、コンソールをいじっていた時に特定のマイクロポストにLikeレコードが複数(今回はuser_id:1 , micropost_id:300 に3つ)作られてしまった事にある。と断定した。

例えばブラウザでホーム画面アクセスしてから、コンソールでいいね(m:300、u:1)生成後ブラウザで♡(m:300)をクリックした。等

解決策

パッと思い付いたのは複合キーでまとめて、一意性を持たせてしまえばええんや!
ただ書き方が分からなかったのでリサーチ。

↓参考
http://yamakichi.hatenablog.com/entry/2016/08/25/003738

なるほど。
アプリケーション側とDB側それぞれの制限の掛け方として記述してあって大変解り易かったです。
(どこかでこの説明受けた気がしてたのですが毎度毎度なるほどと思います)

早速。

モデル
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :micropost
  validates :user_id , {presence: true}
  validates :micropost_id , {presence: true}
  validates  :id, uniqueness: {scope: [:user_id,:micropost_id]}
end
マイグレーションファイル
class AddIndexToLikes < ActiveRecord::Migration[5.1]
  def change
    add_index :likes , [:id , :user_id , :micropost_id], unique: true

  end
end

これでどうだ!

・・・なぜかかわりがなく。なんで!!と20分ほど、無い頭ふりしぼって考えてみた結果
Like_id まで一意にしてしまったことが原因なのではないかと結論、
idはインスタンスが生成されるたび +1ですもんね、考えれば分かる事でした。(いやいや考えたからわかりました!!!)

修正を入れて

モデル
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :micropost
  validates :user_id , {presence: true}
  validates :micropost_id , {presence: true}
  validates  :user_id, uniqueness: {scope: [:micropost_id]}
end
マイグレーションファイルはファイル自体破棄して再度作成
class AddIndexToLikes < ActiveRecord::Migration[5.1]
  def change
    add_index :likes , [:user_id , :micropost_id], unique: true

  end
end

無事完了しました。

まとめ

参考を基に自分で考えてそれをブラウザに形として表す、機能させる。
大変ですがこういった機能の実装は頭がほぐれるようで好きな作業なんだと思います。
レイアウトは触ってませんので不格好ですが自分としては満足です。

次は苦手なテストか、もしくは
1、新機能追加後初のログインユーザーにはフラッシュでお知らせ()
2、検索機能(ユーザー、投稿)

です。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.