Ruby on Rails チュートリアルで30歳までに人生を変える(番外編:シェア=リツイート機能の拡張)
こんにちは。opiyoです。
今回は、番外編:シェア=リツイート機能の拡張をやっていきます。
マイクロポストにリツイートアイコンを表示して、「シェア=リツイート」できるようにします。
ではでは、早速行ってみましょう。
railsチュートリアルシェア=リツイート機能の拡張でやること
仕様
【できたこと】
【できてないこと】
対象画面とイメージ
railsチュートリアルシェア=リツイート機能の拡張の完成版
【view】
# app/views/microposts/_micropost.html.erb <li id="micropost-<%= micropost.id %>"> <% if micropost.retweet_user(current_user) %> <p><%= fa_icon("retweet green", text: "自分がリツイート") %></p> <% end %> <%= 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"> <span id="like"> <%= render "microposts/hart", micropost: micropost %> </span> <% if micropost.retweet_user(current_user) %> <%= link_to fa_icon("retweet green", text: micropost.retweets.count), retwwets_destroy_path(micropost_id: micropost.id) %> <% else %> <%= link_to fa_icon("retweet", text: micropost.retweets.count), retwwets_create_path(micropost_id: 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/retwwets_controller.rb class RetwwetsController < ApplicationController before_action :correct_user, only: [:edit, :update] def create micropost = Micropost.find(params[:micropost_id]) Retweet.create(user_id: current_user.id, micropost_id: micropost.id) # 対象のマイクロポストのupdate_atを現在時刻にする Micropost.find(micropost.id).update_attribute(:updated_at, Time.now) redirect_to root_path end def destroy micropost = Micropost.find(params[:micropost_id]) Retweet.find_by(user_id: current_user.id, micropost_id: micropost.id).destroy # 対象のマイクロポストのupdate_atを現在時刻にする Micropost.find(micropost.id).update_attribute(:updated_at, Time.now) redirect_to root_path end end
# app/controllers/users_controller.rb def show @user = User.find(params[:id]) @microposts = @user.myfeed.paginate(page: params[:page]) .where('content LIKE ?', "%#{params[:search]}%") .reorder(updated_at: :DESC) end
【model】
# app/models/micropost.rb has_many :retweets, dependent: :destroy # シェア=リツイートしたかしてないか確認する def retweet_user(user_id) self.retweets.find_by(user_id: user_id) end
# app/models/retweet.rb class Retweet < ApplicationRecord belongs_to :micropost, counter_cache: :retweets_count belongs_to :user end
# app/models/user.rb # マイクロポストを取得する # 自分と自分がフォローしているユーザー # 自分がリツイート # フォローしているユーザーがリツイート(未実装) def feed following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id" retweet_ids = "SELECT micropost_id FROM retweets WHERE user_id = :user_id" Micropost.where("id IN (#{retweet_ids}) OR user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id) end # 自分が投稿、リツイートしたマイクロポストを取得する def myfeed retweet_ids = "SELECT micropost_id FROM retweets WHERE user_id = :user_id" Micropost.where("id IN (#{retweet_ids}) OR user_id = :user_id", user_id: self.id).order(:updated_at) end
【その他】
# config/routes.rb get 'retwwets/create' get 'retwwets/destroy'
# db/migrate/20170912225854_create_retweets.rb class CreateRetweets < ActiveRecord::Migration[5.0] def change create_table :retweets do |t| t.integer :user_id t.integer :micropost_id t.timestamps end end end
# db/migrate/20170919223052_add_retweets_count_to_microposts.rb class AddRetweetsCountToMicroposts < ActiveRecord::Migration[5.0] def change add_column :microposts, :retweets_count, :integer end end
railsチュートリアルシェア=リツイート機能の拡張で学んだこと
orderを指定してもソート順が変化しない?
自分のタイムラインに表示するポストを更新日順の降順=新しい投稿を上にするをしたくて、最初はこう書いていた。
def show @user = User.find(params[:id]) @microposts = @user.myfeed.paginate(page: params[:page]) .where('content LIKE ?', "%#{params[:search]}%") .order(updated_at: :DESC) end
だけど、なんだか上手くいかないなーと思っていたら犯人を見つけた!
# app/models/micropost.rb default_scope -> { order(created_at: :desc) }
これが定義されていることで、micropost
へのデータ取得はこれが絶対に設定されてしまう。
だから違う設定を反映したい場合は再定義してやる必要があるのだが、解決方法はすごーくシンプル。order
→ reorder
にすればOK
def show @user = User.find(params[:id]) @microposts = @user.myfeed.paginate(page: params[:page]) .where('content LIKE ?', "%#{params[:search]}%") .reorder(updated_at: :DESC) end
リツイートしたポストを表示する
やり方あってるか分からないけど、リツイートしたポストはリツイートテーブルに登録されるのでログインユーザーで引っ掛けて取得。
それをin句を使って取得って感じでやった。
def myfeed retweet_ids = "SELECT micropost_id FROM retweets WHERE user_id = :user_id" Micropost.where("id IN (#{retweet_ids}) OR user_id = :user_id", user_id: self.id).order(:updated_at) end
最初where句の書き方を反対に書いていたのだけど、それだと上手くいかなかったんだが良く分かってない。
これは引き続き調べよう。
トップページのタイムラインにフォローしているユーザーがリツイートしたポストを表示する
やり方としてはこんな感じなのかなぁ
- ログインユーザーがフォローしているユーザーを取得する
- ↑を使ってリツイートテーブルからマイクロポストのIDを取得する
- ↑を使ってマイクロポストテーブルからデータを取得する
自分とフォローしているユーザー、自分とフォローしているユーザーがリツイートしているデータをin句で書けば良いのかなと思ったのだが上手くいかないぁ
- 一応解決したので追記 -
それぞれSQL走ってしまうから、良くないことは分かっているのだが一様解決したのでメモです。
def feed Micropost.where(id: (self.microposts.pluck(:id) + Retweet.where(user_id: self.id).pluck(:micropost_id) + Micropost.where(user_id: self.followers.pluck(:id)).pluck(:id) + Retweet.where(user_id: self.followers.pluck(:id)).pluck(:id) ) ) end
やりたいことを一個づつSQLで取得して、最後にmidropost
のid
に配列で渡して取得する。
railsチュートリアルシェア=リツイート機能の拡張のまとめ
リツイートアイコンの表示やリツイートした数の表示は、いいね機能とほとんど同じなので比較的素直に実装できた。
が、やはりSQL力が全然無い。
複数テーブルを参照しながらデータを取得しないといけないとなると途端に分からなくなる。
以前お世話になった現場の先輩が「プログラムの仕事は7割くらいSQL力」のような事を言っていた事を思い出した。
少し大げさな気がするが、SQL力が大事なことは改めて実感した。
プログラマーは本当に次から次へと覚えることが多いから大変だけど、一つ一つしっかり学んで「こういう時はこーする」みたいな感覚がしっかり掴むことが大事なのかなとも思う。
だからとにかく手を止めずに頑張ろう。