LoginSignup
2
1

More than 5 years have passed since last update.

Rails Tutorial 第14章 簡易まとめ

Posted at

14章 

この章では
1、他のユーザーをフォロー (およびフォロー解除) できるソーシャルな仕組みの追加
2、フォローしているユーザーの投稿をステータスフィードに表示する機能を追加
を目指してチュートリアルの完了を目指す(長かった~~~。。。)

14.1 Relationshipモデル

ユーザーをフォローする機能とは何かを考えると
「1人のユーザーが複数のユーザーをhas_manyとしてフォローし、1人のユーザーに複数のフォロワーがいることをhas_manyで表す」といった関連付けを行っていきたいが問題があるらしい
現時点ではなんのことかさっぱりだ。

$ git checkout -b following-users

14.1.1 データモデルの問題 (および解決策)

問題1・・・英語で表現できない
フォローしてくれている人==follower 〇 → その人達の集合==followers 〇
フォローしてる人(自分がフォローした人(過去形))==followed 〇 → その人達の集合==followeds・・・??????

followeds??????過去形に複数形???
そんな言葉(複数形)英語にありません 

解決策・・・followeds じゃなくてfollowingにしちゃおう(ただし個人はのことはfollowed_idでidもたせるよ)

ってことでfollowing(自分がフォローした人)をhas_manyで関連付ければよくない?
自分のidが、相手にとってのfollower_idになればヨシ
followingテーブルはユーザーの集合なんだからUserテーブルと同じカラムを持つべき(名前メアドパスワード等を持つ)

image.png

問題2・・・このまま進めて行くと仮定しよう。followersテーブル(表)をつくるまでは簡単としても、Userと同じカラムがそのまま入れなければならない訳で、さらに仮定してUser.firstがユーザー名を変更したなら、↑の二つのテーブルのnameカラムも変更しなければならない

解決策・・・自分なりに考えてまとめると、一番大事な事はこのフォロー/フォロワー機能はユーザー1とユーザー2(UserモデルとUserモデル(self))をつなぎたいというシンプルなものであるということ。故に、それを行う為にRelationshipモデルというテーブルを挟み、道筋をつなぐものとして構成するのだ。ユーザー情報はUserテーブルにだけ持たせて(言い換えるとそれぞれの機能が無駄な情報をもつことのないようにする)RelationshipテーブルにはUserテーブル自身(id)と自身を繋ぐ為のカラム(=follower_idとfollowed_idの二つ)だけ持たせる。(自分用にまとめた言葉なので理解不能かもしれない)

image.png

image.png

↑は解り易くしてある「仮定」の図で、フォロー/フォロワー機能は独立して動いているから(Facebookの様な友達関係(相互)ではないということ)”こうやって組めば能動的なやつ(フォローした人)と受動的なやつ(フォローしてきたやつ)判別できるだろ?”ってだけ。
仮ではないテーブルの名前はRelationships
$ rails generate model Relationship follower_id:integer followed_id:integer
image.png

このリレーションシップは今後follower_idとfollowed_idで頻繁に検索することになるので、それぞれのカラムにインデックスを追加します。

db/migrate/[timestamp]_create_relationships.rb
 class CreateRelationships < ActiveRecord::Migration[5.0]
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :followed_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    add_index :relationships, [:follower_id, :followed_id], unique: true<--注目
  end
end

注目・・・複合キーインデックス。”この組合せ”がユニーク(一意)で2回以上フロー出来ないよってなことを保証

$ rails db:migrate

14.1.2 User/Relationshipの関連付け

早速フォローしているユーザー(能動的)とフォロワーを実装する前に、UserとRelationshipの関連付けを行います。
1人のユーザーにはhas_many (1対多) のリレーションシップがあり、このリレーションシップは2人のユーザーの間の関係なので、フォローしているユーザーとフォロワーの両方に属します (belongs_to)。

app/models/user.rb
 class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:  "Relationship",<--注目1
                                  foreign_key: "follower_id",<--注目2
                                  dependent:   :destroy
  .
  .
  .
end

型としてRelationshipsモデルはありますが、上の方であらわした仮のテーブルを実用に回していきたいということで上のようなactive_relationshipsシンボルをつかったコードになる

注目1・・・
rb:class User < ApplicationRecord
has_many :microposts
.
.
.
end

13章の関連付けでは上の様に書いたがこれはrailsがMicropostモデルをシンボルから勝手に探してくれたからで今回の場合
has_many :active_relationships
だけでは仮のテーブルを探しにいってしまう(あるわけない)
のでRails に探して欲しいモデルのクラス名を明示している

注目2・・・データベースの2つのテーブルを繋ぐidは外部キー (foreign key)と呼びます
この外部キーの名前を使って、Railsは関連付けの推測をしています。今回はフォローしているユーザーをfollower_idという外部キーを使って特定しなくてはなりません。
しかし特徴として、Railsはデフォルトでは外部キーの名前を_idといったパターンとして理解し、 に当たる部分からクラス名 (正確には小文字に変換されたクラス名) を推測します
followerというクラス名は存在しないので、ここでもRailsに正しいクラス名を伝える必要が発生します。」

app/models/relationship.rb
 class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
end

image.png

14.1.3 Relationshipのバリデーション

バリデーションを追加します

test/models/relationship_test.rb
 require 'test_helper'

class RelationshipTest < ActiveSupport::TestCase

  def setup
    @relationship = Relationship.new(follower_id: users(:michael).id,
                                     followed_id: users(:archer).id)
  end

  test "should be valid" do
    assert @relationship.valid?
  end

  test "should require a follower_id" do
    @relationship.follower_id = nil
    assert_not @relationship.valid?
  end

  test "should require a followed_id" do
    @relationship.followed_id = nil
    assert_not @relationship.valid?
  end
end
app/models/relationship.rb
 class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
  validates :follower_id, presence: true
  validates :followed_id, presence: true
end

モデル生成の際作られたtest/fixtures/relationships.yml内にある中身はたった今設けたバリデーションをパスできないので消しとく事

GREEN

14.1.4 フォローしているユーザー

1人のユーザーにはいくつもの「フォローする」「フォローされる」といった関係性があります (こういった関係性を「多対多」と呼びます)。
のでhas_manyではなくhas_many throughをつかいます。
デフォルトのhas_many throughという関連付けでは、Railsはモデル名 (単数形) に対応する外部キーを探します。つまり、次のコードでは、

has_many :followeds, through: :active_relationships

Railsは「followeds」というシンボル名を見て、これを「followed」という単数形に変え、 relationshipsテーブルのfollowed_idを使って対象のユーザーを取得してきます。
ただ英語が変だから:sourceパラメーターを使って、「following配列の元はfollowed idの集合である」(言い換えれば名前を置き換えておいて、しかし元はfolloweds)ということを明示的にRailsに伝えます。

app/models/user.rb
 class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:  "Relationship",
                                  foreign_key: "follower_id",
                                  dependent:   :destroy
  has_many :following, through: :active_relationships, source: :followed
  .
  .
  .
end

上の定義した関連付けにより、フォローしているユーザーを配列の様に扱えるようになりました

例えば、include?メソッドを使ってフォローしているユーザーの集合を調べてみたり、findメソッドで関連付けを通してオブジェクトを探しだせるようになったり、followingで取得したオブジェクトは、配列のように要素を追加したり削除したりすることができます。

user.following.include?(other_user)
user.following.find(other_user)
user.following << other_user
user.following.delete(other_user)

賢いメソッドの使い方

話の途中だがこういった配列の中身を参照するメソッドの使い方について賢いつかいかたを一つ。
ユーザーが増える=配列の中身が増え、時には膨大になる事がある。
その時にデータベースから全てを取りだしてメソッドを行うのでは負担が掛かる為(length等)

following.include?(other_user)

user.microposts.count

といったメソッドはデータベース内で直接比較を行って結果をレスポンスしてくれてるので高速で処理を行える。

らしい。

テスト駆動開発でメソッドを作成していく(follow,unfollow,following?(boolean))

本筋にもどしてfollowingで取得した集合をより簡単に取り扱うために、followやunfollowといった便利メソッドを追加しましょう。これらのメソッドは、例えばuser.follow(other_user)といった具合に使います。さらに、これに関連するfollowing?論理値メソッドも追加し、あるユーザーが誰かをフォローしているかどうかを確認できるようにします

test/models/user_test.rb
 require 'test_helper'

class UserTest < ActiveSupport::TestCase
  .
  .
  .
  test "should follow and unfollow a user" do
    michael = users(:michael)
    archer  = users(:archer)
    assert_not michael.following?(archer)
    michael.follow(archer)
    assert michael.following?(archer)
    michael.unfollow(archer)
    assert_not michael.following?(archer)
  end
end

もちろんRED

上に張った表を参考にメソッドを作っていく

app/models/user.rb
 class User < ApplicationRecord
  .
  .
  .
  def feed
    .
    .
    .
  end

  # ユーザーをフォローする
  def follow(other_user)
    active_relationships.create(followed_id: other_user.id)
  end

  # ユーザーをフォロー解除する
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

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

  private
  .
  .
  .
end

GREEN

14.1.5 フォロワー

いままでフォローする側(能動的)を設けてきたのでされる側を扱って行く。
これは上のuser.followingメソッドと対になります。
実際、follower_idとfollowed_idを入れ替えるだけで、フォロワーについてもフォローする場合と全く同じ方法が活用できます

app/models/user.rb
 class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  has_many :active_relationships,  class_name:  "Relationship",
                                   foreign_key: "follower_id",
                                   dependent:   :destroy
  has_many :passive_relationships, class_name:  "Relationship",
                                   foreign_key: "followed_id",
                                   dependent:   :destroy
  has_many :following, through: :active_relationships,  source: :followed
  has_many :followers, through: :passive_relationships, source: :follower
  .
  .
  .
end```

※上に書いた説明から、オプションsourceは消しても問題なし。なぜか分からないなら見直すべし。
ここでは類似的な書き方をしてわかりやすくしている

次に、followers.include?メソッドを使って先ほどのデータモデルをテストしていきます。

```rb:test/models/user_test.rb
 require 'test_helper'

class UserTest < ActiveSupport::TestCase
  .
  .
  .
  test "should follow and unfollow a user" do
    michael  = users(:michael)
    archer   = users(:archer)
    assert_not michael.following?(archer)
    michael.follow(archer)
    assert michael.following?(archer)
    assert archer.followers.include?(michael)<--これ
    michael.unfollow(archer)
    assert_not michael.following?(archer)
  end
end

GREEN

14.2 [Follow] のWebインターフェイス

フォロー/フォロワー機能のデータモデリングが完了したので実際にWebインターフェイスで使ってみることでしょう。

14.2.1 フォローのサンプルデータ

1つ前の章のときと同じように、サンプルデータを自動作成するrails db:seedを使って、データベースにサンプルデータを登録できるとやはり便利です。先にサンプルデータを自動作成できるようにしておけば、Webページの見た目のデザインから先にとりかかることができ、バックエンド機能の実装を後に回すことができます。

リレーションシップのサンプルデータを生成するためのコードです。

db/seeds.rb
 # ユーザー
User.create!(name:  "Example User",
             email: "example@railstutorial.org",
             password:              "foobar",
             password_confirmation: "foobar",
             admin:     true,
             activated: true,
             activated_at: Time.zone.now)

99.times do |n|
  name  = Faker::Name.name
  email = "example-#{n+1}@railstutorial.org"
  password = "password"
  User.create!(name:  name,
               email: email,
               password:              password,
               password_confirmation: password,
               activated: true,
               activated_at: Time.zone.now)
end

# マイクロポスト
users = User.order(:created_at).take(6)
50.times do
  content = Faker::Lorem.sentence(5)
  users.each { |user| user.microposts.create!(content: content) }
end

# リレーションシップ
users = User.all
user  = users.first
following = users[2..50]
followers = users[3..40]
following.each { |followed| user.follow(followed) }
followers.each { |follower| follower.follow(user) }

リレーションシップの部分。

ここでは、
1、最初のユーザーにユーザー3からユーザー51までをフォローさせ、
2、
それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせます。

こういった短文で関係性まで生成出来るようによく参考に。

$ rails db:migrate:reset
$ rails db:seed

14.2.2 統計と [Follow] フォーム

サンプルが用意できたのでビューに反映させていきます

1、プロフィールページとHomeページに、フォローしているユーザーとフォロワーの統計情報を表示するためのパーシャルを作成します。
2、フォロー用とフォロー解除用のフォームを作成します。
3、それから、フォローしているユーザーの一覧 ("following") とフォロワーの一覧 ("followers") を表示する専用のページを作成します。

1の統計情報には、現在のユーザーがフォローしている人数と、現在のフォロワーの人数が表示されています。それぞれの表示はリンクになっており、3にあるような専用の表示ページに移動できます。
先に3のルーティングを設定します

config/routes.rb
 Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'
  resources :users do <--ここからね
    member do
      get :following, :followers
    end
  end
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy]
end

流れがあべこべになりそうなので進めます。←ここで下記のルーティングのメソッドをよむとヨシ

ルーティングを定義したので、統計情報のパーシャルを実装する準備が整いました。このパーシャルでは、divタグの中に2つのリンクを含めるようにします

app/views/shared/_stats.html.erb
 <% @user ||= current_user %>
<div class="stats">
  <a href="<%= following_user_path(@user) %>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
  </a>
  <a href="<%= followers_user_path(@user) %>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
  </a>
</div>

このパーシャルはプロフィールページとHomeページの両方に表示されるので最初の行で現在のユーザーを取得します。
また数の取得に関しては上記の賢いメソッドの使い方を使ってデータベースから引っ張っている

app/views/static_pages/home.html.erb
 <% if logged_in? %>
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="stats">
        <%= render 'shared/stats' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
  </div>
<% else %>
  .
  .
  .
<% end %>
app/assets/stylesheets/custom.scss
 .
.
.
/* sidebar */
.
.
.
.gravatar {
  float: left;
  margin-right: 10px;
}

.gravatar_edit {
  margin-top: 15px;
}

.stats {
  overflow: auto;
  margin-top: 0;
  padding: 0;
  a {
    float: left;
    padding: 0 10px;
    border-left: 1px solid $gray-lighter;
    color: gray;
    &:first-child {
      padding-left: 0;
      border: 0;
    }
    &:hover {
      text-decoration: none;
      color: blue;
    }
  }
  strong {
    display: block;
  }
}

.user_avatars {
  overflow: auto;
  margin-top: 10px;
  .gravatar {
    margin: 1px 1px;
  }
  a {
    padding: 0;
  }
}

.users.follow {
  padding: 0;
}

/* forms */
.
.
.

↓  ↓  ↓
image.png

この後すぐ、プロフィールにも統計情報パーシャルを表示しますが、今のうちに2を達成する [Follow] / [Unfollow] ボタン用のパーシャルも作成してなんなら同時に行う。

app/views/users/_follow_form.html.erb
 <% unless current_user?(@user) %>
  <div id="follow_form">
  <% if current_user.following?(@user) %>
    <%= render 'unfollow' %>
  <% else %>
    <%= render 'follow' %>
  <% end %>
  </div>
<% end %>

このコードは、if文にてfollowとunfollowのパーシャルに作業を振っているだけです。

パーシャルでは、Relationshipsリソース用の新しいルーティングが必要です。

config/routes.rb
 Rails.application.routes.draw do
  root                'static_pages#home'
  get    'help'    => 'static_pages#help'
  get    'about'   => 'static_pages#about'
  get    'contact' => 'static_pages#contact'
  get    'signup'  => 'users#new'
  get    'login'   => 'sessions#new'
  post   'login'   => 'sessions#create'
  delete 'logout'  => 'sessions#destroy'
  resources :users do
    member do
      get :following, :followers
    end
  end
  resources :account_activations, only: [:edit]
  resources :password_resets,     only: [:new, :create, :edit, :update]
  resources :microposts,          only: [:create, :destroy]
  resources :relationships,       only: [:create, :destroy]
end

フォロー/フォロー解除用のパーシャル自体を作って

app/views/users/_follow.html.erb
 <%= form_for(current_user.active_relationships.build) do |f| %>
  <div><%= hidden_field_tag :followed_id, @user.id %></div>
  <%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
app/views/users/_unfollow.html.erb
 <%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete }) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>

これらの2つのフォームでは、いずれもform_forを使ってRelationshipモデルオブジェクトを操作しています。これらの2つのフォームの主な違いは、新しいリレーションシップを作成(create)するのに対し、既存のリレーションシップを見つけ出して、そのあと削除する(destroy)という点です。
これらのふたつはボタンのみを設けたということです
しかし、それでもこのフォームはfollowed_idをコントローラに送信する必要があります。これを行うために、hidden_field_tagメソッドを使います。このメソッドは、次のフォーム用HTMLを生成します。

<input id="followed_id" name="followed_id" type="hidden" value="3" />

隠しフィールドのinputタグを使うことで、ブラウザ上に表示させずに適切な情報を含めることができます。

これでプロフィールページにも表示させることができます

app/views/users/show.html.erb
 <% provide(:title, @user.name) %>
<div class="row">
  <aside class="col-md-4">
    <section>
      <h1>
        <%= gravatar_for @user %>
        <%= @user.name %>
      </h1>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
    </section>
  </aside>
  <div class="col-md-8">
    <%= render 'follow_form' if logged_in? %>
    <% if @user.microposts.any? %>
      <h3>Microposts (<%= @user.microposts.count %>)</h3>
      <ol class="microposts">
        <%= render @microposts %>
      </ol>
      <%= will_paginate @microposts %>
    <% end %>
  </div>
</div>

image.png

このあとアクションを編集してこのボタンが効くように設定していきます

ルーティングのメソッド

↓参考
https://qiita.com/hiyoko/items/f1491e53450cb347606b

memberメソッドを使うとユーザーidが含まれているURLを扱うようになります
この場合のURLは /users/1/following や /users/1/followers のようになります
どちらもデータを表示するページなので、適切なHTTPメソッドはGETリクエストでそれぞれシンボルでメソッドに渡されている

image.png

tutorialでは idを指定せずにすべてのメンバーを表示する、collectionメソッドも紹介されている

resources :users do
  collection do
    get :tigers
  end
end

このコードは /users/tigers というURLに応答します (アプリケーションにあるすべてのtigerのリストを表示します)

14.2.3 [Following] と [Followers] ページ

前節の続きになりますが、3を達成していきたい。

image.png

フォローしてるひと一覧モックアップ

まずはテストから

est/controllers/users_controller_test.rb
 require 'test_helper'

class UsersControllerTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
    @other_user = users(:archer)
  end
  .
  .
  .
  test "should redirect following when not logged in" do
    get following_user_path(@user)
    assert_redirected_to login_url
  end

  test "should redirect followers when not logged in" do
    get followers_user_path(@user)
    assert_redirected_to login_url
  end
end

ログインが必要なページとするテスト

この実装には1つだけトリッキーな部分があります。
それはUsersコントローラに2つの新しいアクションを追加する必要があるということです。memberメソッド内で定義した2つのルーティングにもとづいており、これらはそれぞれfollowingおよびfollowersと呼ぶ必要があります。
それぞれのアクションでは、タイトルを設定し、ユーザーを検索し、@user.followingまたは@user.followersからデータを取り出し、ページネーションを行なって、ページを出力する必要があります。

app/controllers/users_controller.rb
 class UsersController < ApplicationController
  before_action :logged_in_user, only: [:index, :edit, :update, :destroy,
                                        :following, :followers]
  .
  .
  .
  def following
    @title = "Following"
    @user  = User.find(params[:id])
    @users = @user.following.paginate(page: params[:page])
    render 'show_follow'
  end

  def followers
    @title = "Followers"
    @user  = User.find(params[:id])
    @users = @user.followers.paginate(page: params[:page])
    render 'show_follow'
  end

  private
  .
  .
  .
end

Railsは慣習に従って、アクションに対応するビューを暗黙的に呼び出します。例えば、showアクションの最後でshow.html.erbを呼び出す、といった具合です。
一方で上のいずれのアクションも、renderを明示的に呼び出し、show_followという同じビューを出力しています(構造が同じの為)。したがって、作成が必要なビューはこれ1つです。

app/views/users/show_follow.html.erb
 <% provide(:title, @title) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <%= gravatar_for @user %>
      <h1><%= @user.name %></h1>
      <span><%= link_to "view my profile", @user %></span>
      <span><b>Microposts:</b> <%= @user.microposts.count %></span>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
      <% if @users.any? %>
        <div class="user_avatars">
          <% @users.each do |user| %>
            <%= link_to gravatar_for(user, size: 30), user %>
          <% end %>
        </div>
      <% end %>
    </section>
  </aside>
  <div class="col-md-8">
    <h3><%= @title %></h3>
    <% if @users.any? %>
      <ul class="users follow">
        <%= render @users %>
      </ul>
      <%= will_paginate %>
    <% end %>
  </div>
</div>

このとき、上のコードでは現在のユーザーを一切使っていない点に注目してください。
したがって、他のユーザーのフォロワー一覧ページもうまく動きます
コントローラ内のアクションでインスタンス変数@userに何を代入したのか、確認しとくといいかも

↓ユーザーのフォロワー
image.png

↓他のユーザーのフォロワー
image.png

beforeフィルターを既に実装しているため、この時点でテストは green

show_followの描画結果を確認するための統合テスト

$ rails generate integration_test following
invoke test_unit
create test/integration/following_test.rb

Relationshipsのfixtureにいくつかサンプルを。

test/fixtures/relationships.yml
 one:
  follower: michael
  followed: lana

two:
  follower: michael
  followed: malory

three:
  follower: lana
  followed: michael

four:
  follower: archer
  followed: michael

前半の2つでMichaelがLanaとMaloryをフォローし、後半の2つでLanaとArcherがMichaelをフォローしています

test/integration/following_test.rb
 require 'test_helper'

class FollowingTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
    log_in_as(@user)
  end

  test "following page" 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 "followers page" 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
end

正しい数が表示されているかどうかと、正しいURLが表示されているかどうか。
もし@user.following.empty?の結果がtrueであれば、assert_select内のブロックが実行されなくなるため、その場合においてテストが適切なセキュリティモデルを確認できなくなることを防いでいます。(?)

GREEN

14.2.4 [Follow] ボタン (基本編)

いよいよ [Follow] / [Unfollow] ボタンを動作させましょう。フォローとフォロー解除はそれぞれリレーションシップの作成と削除に対応しているため、まずはRelationshipsコントローラが必要

$ rails generate controller Relationships

セキュリティモデルを確立させていきましょう。(アクセス制限のテスト)

test/controllers/relationships_controller_test.rb
 require 'test_helper'

class RelationshipsControllerTest < ActionDispatch::IntegrationTest

  test "create should require logged-in user" do
    assert_no_difference 'Relationship.count' do
      post relationships_path
    end
    assert_redirected_to login_url
  end

  test "destroy should require logged-in user" do
    assert_no_difference 'Relationship.count' do
      delete relationship_path(relationships(:one))
    end
    assert_redirected_to login_url
  end
end

RED

app/controllers/relationships_controller.rb
 class RelationshipsController < ApplicationController
  before_action :logged_in_user

  def create
  end

  def destroy
  end
end

GREEN

[Follow] / [Unfollow] ボタンを動作させるためには、フォームから送信されたパラメータを使って、followed_idに対応するユーザーを見つけてくる必要があります。その後、見つけてきたユーザーに対して適切にfollow/unfollowメソッドを使います。(よく上の方のコードを見て理解を深めて置く事)

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)
    redirect_to user
  end

  def destroy
    user = Relationship.find(params[:id]).followed
    current_user.unfollow(user)
    redirect_to user
  end
end

14.2.5 [Follow] ボタン (Ajax編)

上の機能で申し分ないところにtutorialは改善策を。

上の機能だとユーザーはプロフィールページを最初に表示し、それからユーザーをフォローし、その後すぐ元のページにリダイレクトされるという流れになります

Ajaxを使えば、Webページからサーバーに「非同期」で、ページを移動することなくリクエストを送信することができます。つまりリダレクトすることなくアクションを終了できる。

form_for
上のコードを次のように置き換えるだけです。
form_for ..., remote: true

app/views/users/_follow.html.erb
 <%= form_for(current_user.active_relationships.build, remote: true) do |f| %>
  <div><%= hidden_field_tag :followed_id, @user.id %></div>
  <%= f.submit "Follow", class: "btn btn-primary" %>
<% end %>
app/views/users/_unfollow.html.erb
 <%= form_for(current_user.active_relationships.find_by(followed_id: @user.id),
             html: { method: :delete },
             remote: true) do |f| %>
  <%= f.submit "Unfollow", class: "btn" %>
<% end %>

フォームの更新が終わったので、今度はこれに対応するRelationshipsコントローラを改造して、Ajaxリクエストに応答できるようにしましょう。こういったリクエストの種類によって応答を場合分けするときは、respond_toメソッドというメソッドを使います。

この文法は少々変わっていて混乱を招く可能性がありますが、上の (ブロック内の) コードのうち、いずれかの1行が実行されるという点が重要です(if文みたいな??)
RelationshipsコントローラでAjaxに対応させるために、respond_toメソッドをcreateアクションとdestroyアクションでそれぞれ追加します

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

※アクション内のuser変数をここでインスタンス変酢に変更しています(なぜなのかは理解に至らなかった)

頭がパンクしそうなので一旦suspend
https://railstutorial.jp/chapters/following_users?version=5.1#sec-a_working_follow_button_with_ajax

14.2.6 フォローをテストする

フォローボタンが動くようになったのでテストを。
ユーザーのフォローに対するテストでは、 /relationshipsに対してPOSTリクエストを送り、フォローされたユーザーが1人増えたことをチェックします。
フォロー解除の場合はDELETEリクエストで以下同文

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 "should follow a user the standard way" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, params: { followed_id: @other.id }
    end
  end

  test "should follow a user with Ajax" do
    assert_difference '@user.following.count', 1 do
      post relationships_path, xhr: true, params: { followed_id: @other.id }
    end
  end

  test "should unfollow a user the standard way" 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 "should unfollow a user with 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
end

ここではAjax版のテストものせてます。
xhr :trueオプションを使うようにするだけです。 xhr (XmlHttpRequest)
respond_toでは、JavaScriptに対応した行が実行されるようになります。

GREEN

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1