Ruby on Rails チュートリアルで30歳までに人生を変える(番外編:いいね機能の拡張)
こんにちは。opiyoです。
今回は、番外編:いいね機能の拡張をやっていきます。
マイクロポストにハートアイコンを表示して、「いいね」できるようにします。
ではでは、早速行ってみましょう。
railsチュートリアルのいいね機能の拡張でやること
仕様
「いいね」機能を実装するに当たって、ざっくりだけど仕様を明確にしてみようと思う。
- 一つ一つのマイクロポストに「いいね」することができる
- 「いいね」は一つのマイクロポストに対して一人一回まで
- 「いいね」表示場所は、投稿日下にアイコンを使って表示する(いいね:ハート、削除:ゴミ箱)
- 「いいね」されたら赤いハート、取り消されたら白いハートにする
- 「いいね」された数を表示する(実装中)
- 「いいね」ボタンはajaxで処理する(困ってる)
こんな感じだろうか。一人一回までって制御が出来れば色々サンプルはありそうだし出来そうかな?
対象画面とイメージ
対象になる画面は2つ
【トップページ:app/views/static_pages/home.html.erb】
【ユーザー詳細ページ:app/views/users/show.html.erb】
railsチュートリアルのいいね機能の拡張の完成版
全部乗っけると数が多くなるので、主要な部分を載せます。
【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しています。
色々と試行錯誤している跡がコミット追うと分かりますね。
railsチュートリアルのいいね機能の拡張で学んだこと
いいねの数が変わらない
ハートのアイコンを押したらいいねの数がカウントアップするって処理をしたいのだけど、数が変わらない。
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
する前のオブジェクト。
そりゃー変わらないわ
railsチュートリアルのいいね機能の拡張で分からないこと
いいねボタンの連続で押せない
今の段階だと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
するってのが正しいアプローチなんだとは思う。
railsチュートリアルのいいね機能の拡張のまとめ
まだ完成してないのだが、どうにかこうにか形になってきたので一先ずまとめてみました。
実装時間が長くなってしまったので当時ハマったことが少し思い出せない所もあり、もったい無いなと感じております。
解決できた時は嬉しくて先へ先へ進んじゃいがちだけど、一度立ち止まって何がダメだったのかをしっかり考える→まとめてみるってのは今後やっていこうと思いました。きっとそこが一番成長できる所だと思うので次回出会った時に「あーこの前のあれねって」思えるように。
まだまだ解決できてない部分もあるし、Likeモデルのcounter_cache
の機能もいまいち良く分かって無いので引き続き頑張ろうと思います。
railsチュートリアルのいいね機能の拡張の追記
未解決問題のajaxでの処理を追記
上で書いた2つの問題点を解決することに成功しましたので、追記でまとめます!
問題点
上に書いてある通りですが、大きく二つ
- クラスの付けかけだけしてるので、link_toのパスが変わらない
- いいねの数が消える
これに対するアプローチは、やはり
更新したい場所のみを再度renderする
うん。アプローチはやはり間違ってなかった!
解決したソース
とりあえずソースを貼ります!
【「いいね」前】
【「いいね」後】
# app/views/microposts/_micropost.html.erb <span class="action"> <span id="like"> # ajaxで処理する時に「どこを差し替える」が必要なので、それを追加 <%= render "microposts/hart", micropost: micropost %> # この部分を`_hart`としてテンプレート化する! </span> <% if current_user?(micropost.user) %> <%= link_to fa_icon("trash"), micropost, method: :delete, data: {confirm: "You sure?"}, micropost_id: micropost.id %> <% end %> </span>
# app/views/microposts/_hart.html.erb <% 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 %>
# app/views/likes/create.js.erb $("#like").html("<%= escape_javascript(render 'microposts/hart', micropost: @micropost) %>"); console.log("ajaxで処理")
# app/views/likes/destroy.js.erb $("#like").html("<%= escape_javascript(render 'microposts/hart', micropost: @micropost) %>"); console.log("ajaxで処理")
解決のポイント
アプローチの仕方は間違ってなかった。ようはhart
の部分だけ再度描画させたかったので、その部分を外出ししてそこだけ描画させる。
描画のやり方がjsのファイルで書いてあるやつだ。
like
クラスを持ったspan
タグの中身をhtml
メソッドで全て置き換えているイメージ。
<%= escape_javascript(render 'microposts/hart', micropost: @micropost) %>
の部分は極端に言うと展開されたhtmlになるようなイメージ。
だからこんな感じなのかな。
$("#like").html(<a class="micropost301" data-remote="true" href="/likes/create?micropost_id=301"> <i class="fa fa-heart-o"></i> 0 </a>)
やっていく中でハマったポイントはmicropost
インスタンスの渡し方。
最初jsのファイルにはmicropost: @micropost
を書いて無くてずっとエラーになっていた。
Completed 500 Internal Server Error in 181ms (ActiveRecord: 2.7ms) ActionView::Template::Error (undefined local variable or method `micropost' for #<#<Class:0x007fe0a1a1a368>:0x007fe0a6dc1110> Did you mean? @micropost):
micropost
がねーよってことですね。だからコントローラーで取得したインスタンスを渡してあげて解決!
railsチュートリアルのいいね機能の拡張の再まとめ
ajaxの処理にめちゃくちゃハマって、すげー時間を使ってしまった。
最終的には教えてもらう形で解決出来たのだが、本当に感謝感謝感謝ですね。
よく未経験エンジニアからエンジニアへっていう記事がありますが、そこに絶対書かれているのが
メンターを見るけること
これは絶対そー思います。僕が3日くらい悩んでいたものが30分で解決出来てしまうのですから間違い無いです。
いつでも相談出来るちゃうものや無料でプログラミグを教えてくれて転職支援までしてくれるサービスなど本当にいっぱいあるので一度覗いてみてください。
ちなみに、今回ajax処理を頑張って実装しましたが、別に無くても良いんじゃ無い?という話もありました。
普通にhtmlで返しても全く動きは同じだし、このぐらいのレベルならば正直あまりメリットは無い。だからスピードが遅くてダメだーってなってから対策するってアプローチの方で問題無いって。
一つのことにこだわることも大事だけど、別の事もやりたいとか全体が終わってないとかなるぐらいならば思い切って後回しってのは良いアプローチなのかもしれないですね。
ってな感じでした。