おぴよの気まぐれ日記

おぴよの気まぐれ日記

岡山やプログラミング、ファッションのこと、子育てや人生、生き方についての備忘録。

30歳まで残り2年の僕は人生を変えるためにRailsチュートリアルを始めようと思う(番外編:シェア=リツイート機能の拡張)

こんにちは。opiyoです。

今回は、番外編:シェア=リツイート機能の拡張をやっていきます。

マイクロポストにリツイートアイコンを表示して、「シェア=リツイート」できるようにします。

ではでは、早速行ってみましょう。

やること

仕様

【できたこと】

  • リツイートアイコンを表示する
  • リツイートされた数を表示する
  • リツイートしたポストがタイムラインにも表示される
  • 自分のタイムラインにてリツイートしたポストを考慮してソートできてない

【できてないこと】

  • フォローしている人がリツイートしたポストがトップページにも表示される
  • 誰がリツイートしたのかポストに表示する

対象画面とイメージ

f:id:opiyotan:20170922135854p:plain

完成形(まだ途中だけど)

【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

ハマった/気づきポイント

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 へのデータ取得はこれが絶対に設定されてしまう。

だから違う設定を反映したい場合は再定義してやる必要があるのだが、解決方法はすごーくシンプル。orderreorderにすれば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で取得して、最後にmidropostidに配列で渡して取得する。

  • 自分が投稿したポスト
  • 自分がリツイートしたポスト
  • 自分がフォローしているユーザーが投稿したポスト
  • 自分がフォローしているユーザーがリツイートしたポスト

まとめ

リツイートアイコンの表示やリツイートした数の表示は、いいね機能とほとんど同じなので比較的素直に実装できた。

が、やはりSQL力が全然無い。

複数テーブルを参照しながらデータを取得しないといけないとなると途端に分からなくなる。

以前お世話になった現場の先輩が「プログラムの仕事は7割くらいSQL力」のような事を言っていた事を思い出した。

少し大げさな気がするが、SQL力が大事なことは改めて実感した。

プログラマーは本当に次から次へと覚えることが多いから大変だけど、一つ一つしっかり学んで「こういう時はこーする」みたいな感覚がしっかり掴むことが大事なのかなとも思う。

だからとにかく手を止めずに頑張ろう。