こんにちは。opiyoです。
今回は、番外編:いいね機能の拡張をやっていきます。
マイクロポストにハートアイコンを表示して、「いいね」できるようにします。
ではでは、早速行ってみましょう。
やること
仕様
「いいね」機能を実装するに当たって、ざっくりだけど仕様を明確にしてみようと思う。
- 一つ一つのマイクロポストに「いいね」することができる
- 「いいね」は一つのマイクロポストに対して一人一回まで
- 「いいね」表示場所は、投稿日下にアイコンを使って表示する(いいね:ハート、削除:ゴミ箱)
- 「いいね」されたら赤いハート、取り消されたら白いハートにする
- 「いいね」された数を表示する(実装中)
- 「いいね」ボタンはajaxで処理する(困ってる)
こんな感じだろうか。一人一回までって制御が出来れば色々サンプルはありそうだし出来そうかな?
対象画面とイメージ
対象になる画面は2つ
【トップページ:app/views/static_pages/home.html.erb】
【ユーザー詳細ページ:app/views/users/show.html.erb】
完成形(まだ途中だけど)
全部乗っけると数が多くなるので、主要な部分を載せます。
【view】
# app/views/likes/create.js.erb
$(".micropost<%= @micropost.id %> i").addClass("fa-heart");
$(".micropost<%= @micropost.id %> i").removeClass("fa-heart-o");
// $(".micropost<%= @micropost.id %>").text("<%= @micropost.likes_count %>"); # これやるとアイコンが消えちゃう
# app/views/likes/destroy.js.erb
$(".micropost<%= @micropost.id %> i").addClass("fa-heart-o");
$(".micropost<%= @micropost.id %> i").removeClass("fa-heart");
// $(".micropost<%= @micropost.id %>").text("<%= @micropost.likes_count %>"); # これやるとアイコンが消えちゃう
# app/views/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">
<%= micropost.content %>
<%= image_tag micropost.picture.url if micropost.picture? %>
</span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
<span class="action">
<% if micropost.like_user(current_user.id) %> # ----- ここから -----
<%= link_to fa_icon("heart", text: micropost.likes_count), likes_destroy_path(micropost_id: micropost.id), remote: true, class: "micropost#{micropost.id}" %>
<% else %>
<%= link_to fa_icon("heart-o", text: micropost.likes_count), likes_create_path(micropost_id: micropost.id), remote: true, class: "micropost#{micropost.id}" %>
<% end %>
<% if current_user?(micropost.user) %>
<%= link_to fa_icon("trash"), micropost, method: :delete, data: {confirm: "You sure?"}, micropost_id: micropost.id %>
<% end %> # ----- ここまで -----
</span>
</li>
【controller】
# app/controllers/likes_controller.rb
class LikesController < ApplicationController
def create
micropost = Micropost.find(params[:micropost_id])
Like.create(user_id: current_user.id, micropost_id: micropost.id)
@micropost = Micropost.find(params[:micropost_id])
respond_to do |format|
format.html { redirect_to root_path}
format.js
end
end
def destroy
micropost = Micropost.find(params[:micropost_id])
Like.find_by(user_id: current_user.id, micropost_id: micropost.id).destroy
@micropost = Micropost.find(params[:micropost_id])
respond_to do |format|
format.html { redirect_to root_path}
format.js
end
end
end
【model】
# app/models/like.rb class Like < ApplicationRecord belongs_to :micropost, counter_cache: :likes_count belongs_to :user end
# app/models/micropost.rb class Micropost < ApplicationRecord has_many :likes, dependent: :destroy end
【routes】
# config/routes.rb get 'likes/create' get 'likes/destroy'
【migration】
class CreateLikes < ActiveRecord::Migration[5.0]
def change
create_table :likes do |t|
t.integer :user_id
t.integer :micropost_id
t.timestamps
end
end
end
class AddIndexToLikesUserIdAndMicropostId < ActiveRecord::Migration[5.0]
def change
add_index :likes, [:user_id, :micropost_id], unique: true
end
end
class AddlikesCountToMicroposts < ActiveRecord::Migration[5.0]
def change
add_column :microposts, :likes_count, :integer
end
end
githubにpushしています。
色々と試行錯誤している跡がコミット追うと分かりますね。
ハマった/気づきポイント
いいねの数が変わらない
ハートのアイコンを押したらいいねの数がカウントアップするって処理をしたいのだけど、数が変わらない。
DB見ると間違いなく登録されている。何が問題なのかなーと思っていたら…馬鹿やろうだった。
# app/controllers/likes_controller.rb
def create
micropost = Micropost.find(params[:micropost_id])
Like.create(user_id: micropost.user_id, micropost_id: micropost.id)
@micropost = Micropost.find(params[:micropost_id]) # これしてなかった
respond_to do |format|
format.html { redirect_to root_path}
format.js
end
end
createした後にデータを再取得する処理をしてなかったので、viewに返しているデータはcreateする前のオブジェクト。
そりゃー変わらないわ
未解決問題
いいねボタンの連続で押せない
今の段階だとajaxでアイコンといいねの数だけ切り替えてしまっている。
これだと切り替えたあと、link_toのパスとかが変わらないからおかしな状況になる。
だからきっと、その部分だけを別ファイルにしてrender hogeみたいにしてやればいいのかなって思ってる。
いいねの数を書き換えるとアイコンが消える!
やりたいことは、いいねの数を更新させたいだけなのだけど、単純に.text("hoge")ってやると子階層にあるタグが消えちゃう。
<div id="parent">
親要素
<p>子要素</p>
</div>
<button id="button">変更する</button>
$("#button").on("click", function(){
$("#parent").text("変更後の文章");
});
<div id="parent">
変更後の文章
</div>
こんな状況の時、pタグは消えます!
これはどうすればいいじゃろうか。多分根本的には上で書いたようにviewをrenderするってのが正しいアプローチなんだとは思う。
最後に
まだ完成してないのだが、どうにかこうにか形になってきたので一先ずまとめてみました。
実装時間が長くなってしまったので当時ハマったことが少し思い出せない所もあり、もったい無いなと感じております。
解決できた時は嬉しくて先へ先へ進んじゃいがちだけど、一度立ち止まって何がダメだったのかをしっかり考える→まとめてみるってのは今後やっていこうと思いました。きっとそこが一番成長できる所だと思うので次回出会った時に「あーこの前のあれねって」思えるように。
まだまだ解決できてない部分もあるし、Likeモデルのcounter_cacheの機能もいまいち良く分かって無いので引き続き頑張ろうと思います。