Naomi's notebook

Naomi's notebook

Ruby on Rails tutorial +alpha(4)フォローを承認制にする

注意:この記事はメモなので、この記事に書いてあることをやっただけでは同じものができません。(大体の雰囲気は書いてあると思います。)

注意:全くのweb開発初心者なので、間違っているところなどがあるかと思いますが、教えていただけると嬉しいです。

naomi-notebook.hatenablog.com

フォローを承認制にしていきます。

フォロー関係を作った時と同様に FollowRequestモデルを作り、以下の機構を実装していきます。

  • follow_requestsテーブルにfollower_idとfollowed_idを追加、usersと紐づける
  • follower_idの人がフォローリクエスト申請ボタンを押したら テーブルにデータ作成 *followed_idの人がフォロー許可を押したらrelationshipsテーブルにデータを作成してfollow_requestsのデータは削除

以下の動作が保証されなければいけないでしょう。

  • follow_requestsをみられるのは関係する2人のみ
  • follow_requestsを送信できるのはfollower_idだけ
  • follow_requestsを承認できるのはfollowed_idだけ
  • 存在しないフォローリクエストは承認できない

モデルの作成

モデルを作ります。

rails generate model FollowRequest follower_id:integer followed_id:integer

migration

class CreateFollowRequests < ActiveRecord::Migration[6.0]
  def change
    create_table :follow_requests do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :follow_requests, :follower_id
    add_index :follow_requests, :followed_id
    add_index :follow_requests, [:follower_id, :followed_id], unique: true
  end
end

model/follow_request.rb

class FollowRequest < ApplicationRecord
    belongs_to :follower, class_name: "User"
    belongs_to :followed, class_name: "User"
    validates :follower_id, presence: true
    validates :followed_id, presence: true
end

model/user.rb

has_many :active_requests, class_name:  "FollowRequest",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  has_many :passive_requests, class_name:  "FollowRequest",
                                  foreign_key: "followed_id",
                                  dependent:   :destroy
  has_many :request_following, through: :active_requests, source: :followed
  has_many :request_followers, through: :passive_requests, source: :follower
rails db:migrate:reset

テストを書いてフォローリクエストが送れるようにします。 test/models/follow_request_test.rb

test "フォローリクエストを送る" do
    michael = users(:michael)
    archer  = users(:archer)
    assert_not michael.request_following?(archer)
    michael.request_follow(archer)
    assert michael.request_following?(archer)
    assert archer.request_followers.include?(michael)
    michael.request_unfollow(archer)
    assert_not michael.request_following?(archer)
  end

models/user.rb

# ユーザーをフォロー申請する
  def request_follow(other_user)
    request_following << other_user
  end

  # ユーザーをフォロー申請解除する
  def request_unfollow(other_user)
    active_requests.find_by(followed_id: other_user.id).destroy
  end

  # 現在のユーザーがフォローしてたらtrueを返す
  def request_following?(other_user)
    request_following.include?(other_user)
  end

まずフォローリクエストの数をホームに表示することにして、そこをクリックするとフォローリクエストが見られるようにします。 routes.rb

resources :users do
    member do
      get :following, :followers
      get :request_following, :request_followers
      get :events
    end
  end

だいたい同じように作ります。

コントローラとビューの作成

ここまではRelationshipsを作る時と同じでした。ここから、フォローボタンを押すとフォローリクエストが送られるようにします。 (フォローの時のコードと同じようにやる)

次に、ユーザの下のボタンでフォローを承認できるようにします

class RelationshipsController < ApplicationController
    before_action :logged_in_user
  
    def create
      @user = User.find(params[:follower_id])
      if @user.request_unfollow(current_user)
        @user.follow(current_user)
        respond_to do |format|
          format.js {render inline: "location.reload();" }
        end
      else
        flash[:danger]="エラーが発生しました。フォローリクエストが存在しません。" 
        redirect_to :back
      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
<li>
  <%= image_tag user.icon_image(size:50), :size=>"50x50"%>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>
  <% if current_user.request_follower?(user) %>
    <%= form_with(model: current_user.active_relationships.build, remote: true) do |f| %>
      <div><%= hidden_field_tag :follower_id, user.id %></div>
      <%= f.submit "フォロー承認", class: "btn btn-primary" %>
    <% end %>
  <%end%>
</li>

できました。 f:id:Naomi_Lilienthal:20200816234856p:plainf:id:Naomi_Lilienthal:20200816234912p:plain

権限設定・テストなど

あとはテストなどを書いていきます。createを使って直接フォローができないようになっているか注意しましょう。

自分以外は/users/1/request_followersと/users/1/request_folloingを見られない

before_actionを指定すれば良いです。画面上に存在するボタンを押したらルートに飛ばさせるのも変なので、ビューの方でcurrent_user=@user以外の時はリンクを消しておきました。

フォロー関係のテスト

folloing_testを変更すればいいです。

  test "should send and accept follow request a user the standard way" do
    assert_difference '@user.request_following.count', 1 do
      post follow_requests_path, params: { followed_id: @other.id }
    end
    log_in_as(@other)
    assert_difference '@user.following.count', 1 do
      post relationships_path, params: { follower_id: @user.id }
    end
  end

  test "should accept follow request a user with Ajax" do
    assert_difference '@user.request_following.count', 1 do
      post follow_requests_path, xhr: true, params: { followed_id: @other.id }
    end
    log_in_as(@other)
    assert_difference '@user.following.count', 1 do
      post relationships_path,xhr:true, params: { follower_id: @user.id }
    end
  end

  test "should not accept not existing follow request" do
    log_in_as(@other)
    assert_no_difference '@user.following.count' do
      post relationships_path, params: { follower_id: @user.id }
    end
  end

これだとajaxではなくフォローを承認したときにテストが失敗してしまいます。これはformat.htmlの方を返していなかったからです。

def create
      @user = User.find(params[:follower_id])
      if @user.request_unfollow(current_user)
        @user.follow(current_user)
        respond_to do |format|
          format.html {redirect_back(fallback_location: root_path) }
          format.js {render inline: "location.reload();" }
        end
      else
        flash[:danger]="エラーが発生しました。フォローリクエストが存在しません。" 
        redirect_back(fallback_location: root_path)
      end
    end

にしたら通りました。(あとrails5以上ではredirect_to :backが消えていたみたいです。redirect_back(fallback_location: root_path)にしました。) あとは細かい訂正をすれば(省略)テストに全て通過します。