コミュ障だから明日が僕らをよんだって返事もろくにしなかった

何かを創る人に憧れたからブログをはじめたんだと思うよ

社会のボトムズがRailsに手を出す #14

これまでのあらすじ

はじまりがあればおわりがあるとはよく言ったもので、ながらく続いたこの旅路も終わりを迎えます。この旅路で得たものはなんなのか?その結論をようやく下せるような気がします。


前回記事
inujini.hatenablog.com


冒頭文章を書いたときすげーまじめなこと書いているって思ったんですけど "あらすじ" じゃなかった……。そんなわけで前回はマイクロポスト関連をつくりました。今回はフォロー機能をつくります。

ユーザをフォローする

まずは、フォロー関係のモデルをつくるためにRelationshipモデルをつくります。

$ rails generate model Relationship follower_id:integer followed_id:integer
$ rails db:migrate

なんか文章読んでいたら色々構造がぐちゃぐちゃしてきたんで雑にざっくりER図でまとめてみた。
f:id:andron:20190128191416p:plain
割とシンプルになった気がする。正しいのかは知らんけどノリ的にはそんな感じな気がしている。ぶっちゃけるとER図つくれるツール見つけたから試してみたかったってだけなんだけどね…。

利用ツール
Flowchart Maker & Online Diagram Software


そうしたらERBとかをなんやかんやいじってフォロワー数などを表示させるようにします。
f:id:andron:20190127133928p:plain

いじった箇所 説明
config/routes.rb Relationshipリソース用のルーティングを追加
app/models/user.rb フォローユーザ関連のメソッドの実装
app/models/relationship.rb Relationshipモデルに対してバリデーションなどを追加
app/views/static_pages/home.html.erb ホーム画面に統計情報表示
app/views/users/show_follow.html.erb フォローしているユーザーとフォロワーの両方を表示
app/views/users/show.html.erb フォロワーの統計情報を追加
app/views/users/_follow_form.html.erb フォロー/フォロー解除フォームのパーシャル
app/views/users/_follow.html.erb ユーザーをフォローするフォーム
app/views/users/_unfollow.html.erb ユーザーをフォロー解除するフォーム
app/assets/stylesheets/custom.scss CSSカスタマイズ

とまあそんなところをいじっていきます。ほぼほぼガワをいじってますね……。

フォローボタンの機能を実装しよう

これ作ります。
f:id:andron:20190127141927g:plain

$ rails generate controller Relationships
# app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
    before_action :logged_in_user
    def create
      @user = User.find(params[:followed_id])
      current_user.follow(@user)
      respond_to do |format|
        format.html { redirect_to @user }
        format.js
      end
    end
  
    def destroy
      @user = Relationship.find(params[:id]).followed
      current_user.unfollow(@user)
      respond_to do |format|
        format.html { redirect_to @user }
        format.js
      end
    end
end

わざわざリダイレクトするのを避け、Ajax対応させるためにform_for ..., remote: trueとか使います。Railsだとform_for ..., remote: trueとかにするだけで対応できるそうですね。

修正箇所 説明
app/views/relationships/create.js.erb フォローの関係性を作成する
app/views/relationships/destroy.js.erb フォローの関係性を削除する
app/controllers/relationships_controller.rb リレーションシップのアクセス制御

Ajax関連参考
Rails で JavaScript を使用する | Rails ガイド


ステータスフィードをつくろう

以下機能を書き書きしてステータスフィードを実装します。

# app/models/user.rb
class User < ApplicationRecord
### 略 ###
  # ユーザーのステータスフィードを返す
  def feed
    following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"
    Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id)
  end
### 略 ###
end

割とSQLの直書きって抵抗あるんですけど、クエリビルダも万能じゃないから複雑になったらこう使うよってことでいいんですかね。




そんでテストは以下。

$ rails generate integration_test following
# test/integration/following_test.rb
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
  def setup
    @user = users(:michael)
    @other = users(:archer)
    log_in_as(@user)
  end

  test "フォローページ" do
    get following_user_path(@user)
    assert_not @user.following.empty?
    assert_match @user.following.count.to_s, response.body
    @user.following.each do |user|
      assert_select "a[href=?]", user_path(user)
    end
  end

  test "フォロワーページ" do
    get followers_user_path(@user)
    assert_not @user.followers.empty?
    assert_match @user.followers.count.to_s, response.body
    @user.followers.each do |user|
      assert_select "a[href=?]", user_path(user)
    end
  end

  test "標準的な方法でユーザフォロー" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, params: { followed_id: @other.id }
    end
  end

  test "Ajaxでユーザフォロー" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, xhr: true, params: { followed_id: @other.id }
    end
  end

  test "標準的な方法でユーザ解除" do
    @user.follow(@other)
    relationship = @user.active_relationships.find_by(followed_id: @other.id)
    assert_difference '@user.following.count', -1 do
      delete relationship_path(relationship)
    end
  end

  test "Ajaxでユーザ解除" do
    @user.follow(@other)
    relationship = @user.active_relationships.find_by(followed_id: @other.id)
    assert_difference '@user.following.count', -1 do
      delete relationship_path(relationship), xhr: true
    end
  end

  test "Home のフィード" do
    get root_path
    @user.feed.paginate(page: 1).each do |micropost|
      assert_match CGI.escapeHTML(micropost.content), response.body
    end
  end  
end
第十四章の感想

やっと終わったー。テレビアニメ1クール完走できないことに定評のある僕でもこの長丁場を終わらせることが出来ました。さてさて、内容の方ですが、やっていることはフォロー機能の実装となります。色々とリレーションでごちゃごちゃしてますが、実装内容はシンプルにフォローフラグをオンにするかオフにするかといった感じっぽいです。そこを実装の起点にすればオンオフだけじゃなくてユーザIDも紐づけないとダメやなみたいな感じで必要機能を拡張できると思います。

第14章の感想としてはそんな感じです。総括のほうはまた別の記事にまとめていきたいと思います。


終わり