Ruby on Rails チュートリアル 第14章 データモデルの関連付け(フォロー フォロー解除)フィードの実装など 演習 解答

#近況報告

エンジニア転職成功しました。YouTubeもはじめました。

https://www.youtube.com/channel/UCcXQG8lu_EdyP2Cbhtz1bcA


[前回の続き](https://qiita.com/yuuki_netb/items/bd51658712345807c741)

著者略歴
著者：[YUUKI](https://twitter.com/bitcoinjpnnet)
ポートフォリオサイト：[Pooks](https://pooks.herokuapp.com/)
現在：RailsTutorial2周目

#第14章　ユーザーをフォローする　難易度 ★★★★　5時間
<b>挫折しないRailsチュートリアルの進め方を先にお読みください↓↓</b>

[Railsチュートリアルで挫折しない３つのポイント](https://webnewage.com/rails-tutorial-point)

この章では、他のユーザーをフォローしたり、フォロー解除したりするソーシャル的な仕組みと、
フォローしているユーザーの投稿をステータスフィード(いわゆるタイムライン)
に表示する仕組みを追加する。

そのために、まずは**ユーザー間の関係性をどうモデリングするかについて学ぶ。**

その後、モデリング結果に対応するWebインターフェースを実装していく。

Webインターフェースの例として**Ajax**についても後に詳解する。

最後に、ステータスフィードの完成版を実装する。


この最終章では、本書の中で最も難易度の高い手法をいくつか使っている。
その中には、ステータスフィードの作成のためにRuby/SQLを騙すテクニックも含まれる。

この章の例全体にわたって、これまでよりも複雑なデータモデルを使います。

ここで学んだデータモデルは、今後自分用のWebアプリケーションを開発するときに必ず役に立つ。

この章で学ぶことは今まで最も難易度が高いため、
コードを書く前に一旦インターフェースの流れを理解する。

モックアップはこちら

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/a15dd0d9-dfaf-2824-81f6-411a3a0e6d6d.png)

出典：[図 14.1: 現在のプロフィールページ](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/da86ecbb-0480-fffb-90de-c1ff98eb2e93.png)

出典：[図 14.2: フォローする相手を見つける](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/0d4d7602-eddc-3688-5c2c-3e0d6bf7266e.png)

出典：[図 14.3: ユーザーのプロフィール画面に [Follow] ボタンが表示されている
](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/93d85265-36a3-808a-2324-2d54d06b277c.png)

出典：[図 14.4: プロフィールに [Unfollow] ボタンが表示され、フォロワーのカウントが1つ増えた](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/4fcf1307-0fa5-166c-7394-cf4df1747184.png)

出典：[図 14.5: Homeページにステータスフィードが表示され、フォローのカウントが1増えた](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)


ページ操作の全体的なフローは次の通りとなる。

①あるユーザーは(John Calvin)は自分のプロフィールページを最初に表示する
②フォローするユーザーを選択するためにUsersページに移動する
③Calvinは2番目のThomas Hobbesを表示し、Followボタンを押してフォローする
④Homeページに戻ると、followingカウントが1人増える
⑤Hobbesのマイクロポストがステータスフィードに表示されるになる


##14.1 Relationshipモデル

ユーザーをフォローする機能を実装する第一歩は、**データモデルを構成する。**

今回のデータモデルは単純ではなく、
has_many(1対多)の関連付けを用いて
「1人のユーザーが複数のユーザーをhas_manyとしてフォローし、1人のユーザーに複数のフォロワーがいることをhas_manyで表す」
といった方法でも実装できる。

しかし、この方法ではたちまち壁に突き当たってしまう。

これを解決する為の`has_many_through`についても解説する。

Gitユーザーはこれまで同様新しいトピックブランチを作成する

```ruby
$ git checkout -b following-users
```

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

ユーザーをフォローするデータモデル構成のための第一歩として、典型的な場合を検討してみる。

あるユーザーが、別のユーザーをフォローしているところを考えてみる。

```
- CavinはHobbesをフォローしている。
- 逆から見ればHobbesはCalvinからフォローされている。
- CalvinはHobbesから見ればフォロワーであり、Calvinがhobbesをフォローしたことになる。
- Railsにおけるデフォルトの複数形の慣習に従えば、あるユーザーをフォローしているすべてのユーザーの集合(フォローされてる人数)はfollowersとなり、user.followersはそれらのユーザーの配列を表すことになる。
- しかし、これを逆で考えた場合(フォローしている人数）、英語の文法的`followeds`となり、英語の文法からも外れてしまう
- そこで、Railsではフォロー人数を`following`という呼称を採用している。
- したがって、あるユーザーがフォローしている全てのユーザーの集合は`calvin.following`となる
```

つまり、`followers`がフォロワー人数で、`following`がフォロー人数を表すデータの表となる。


まずは`following`テーブル(フォロー人数)を見ていく。

followingテーブルとhas_many関連付けを使って、フオローしているユーザーのモデリングができる。
`user.following`はユーザーの集合でなければならないため、followingテーブルのそれぞれの行は、followed_idで識別可能なユーザーでなければならない。

さらに、それぞれの行はユーザーなので、これらのユーザーに**名前(name)やパスワード(password)などの属性**を追加する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/05e61cfb-0dac-a34e-4911-13759de1b598.png)

出典：[図 14.6: フォローしているユーザーの素朴な実装例](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)


**上記のデータモデルの問題点は非常に無駄が多いこと。**

各行には、フォローしているユーザーのidのみならず、名前やメールアドレスまである。

これらはいずれも`usersテーブル`に既にあるものばかり。

さらによくないことに、followersの方をモデリングする時にも、
同じくらい無駄の多いfollowersテーブルを別に作成しなければならなくなってしまう。

結論としては、このデータモデルはメンテナンスの観点から見て悪夢。
というのも、ユーザー名を変更するたびに、usersテーブルのそのレコードだけでなく、followingテーブルとfollowersテーブルの両方について、そのユーザーを含む全ての行を更新しなければならなくなる。

この問題の根本は、必要な抽象化を行なっていないことである。
正しいモデルを見つけ出す方法の１つは、
**Webアプリケーションにおけるfollowingの動作をどのように実装するかをじっくり考えることにある。**

7章において、RESTアーキテクチャは、作成されたり削除されたりするリソースに関連していたことを思い出してみる。

ここで２つの疑問点が挙げられる。

①あるユーザーが別のユーザーをフォローする時、何が作成されるか？
②あるユーザーが別のユーザーをフォロー解除する時、何が削除されるか？

この点を踏まえて考えると、この場合アプリケーションによって作成または削除されるのは
**2人のユーザーの関係(リレーションシップ)**であることがわかる。

つまり、1人のユーザーは1対多の関係を持つことができ、さらにユーザーはリレーションシップを経由して多くのfollowing(またはfollowers)と関係を持つことができるということ。


このデータモデルには他にも解決しなくてはいけない問題がある。
Facebookのような友好関係(Friendships)では、本質的に**左右対称のデータモデルが成り立つ**が、
Twitterのようなフォロー関係では左右非対称の性質がある。

すなわち、**CalvinはHobbesをフォローしていても、HobbesはCalvinをフォローしていないといった関係性が成り立つ。**

このような左右非対称な関係性を見分けるために、それぞれを
能動的関係(Active Relationship)と
受動的関係(Passive Relationship)と呼ぶことにする。

例えば先ほどの事例のような、CalvinがHobbesをフォローしているが、hobbesはCalvinをフォローしていない場合では、CalvinはHobbesに対して能動的関係を持っていることになる。

逆に、HobbesはCalvinに対して受動的関係を持っていることになる。

まずは、フォローしているユーザーを生成するために、能動的関係に焦点を当てていく。
(受動的関係についてはのちに考える)

先ほどのfollowingデータモデルは実装のヒントにして考える。

フォローしているユーザーは`followed_id`があれば識別することができるので、先ほどの`following`テーブルを`active_relationships`(能動的関係)テーブルと見立ててみる。

ただし、ユーザー情報は無駄なので、ユーザーid以外の情報は削除する。
そして、followed_idを通して、usersテーブルのフォローされているユーザーを見つけるようにする。

このデータモデルを模式図にすると、以下のようになる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/12cd3181-e0fd-d9dd-eafd-4a229a45d6f6.png)

出典：[図 14.7: 能動的関係をとおしてフォローしているユーザーを取得する模式図](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)

間に`active_relationships`を挟むことで、フォローとフォロワーの関係性がスムーズに繋がっている。

能動的関係も受動的関係も、最終的にはデータベースの同じテーブルを使うことになる。

したがって、テーブル名にはこの「関係」を表す「relationships」を使う。

モデル名はRailsの慣習にならって、Relationshipとする。
作成したRelationshipデータモデルを以下に示す。

1つのrelationshipsテーブルを使って２つのモデル(能動的関係と受動的関係)をシミュレートする方法については後に説明する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/6c9b2381-c5e4-3ba9-cd0e-a08b62468a9c.png)


出典：[図 14.8: Relationshipデータモデル](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)

このデータモデルを実装するために、まずは上記のデータモデルに対応したマイグレーションを生成する。

```ruby
$ rails g model Relationship follower_id:integer followed_id:integer
```

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

```rb:[timestamp]_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.1]
  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
```

複合キーインデックスで、`follower_id`と`followed_id`の組み合わせが必ずユニークであることを保証する仕組みを作っている。

これにより、**あるユーザーが同じユーザーを2回以上フォローすることを防いでいる。**


もちろん、このような重複が起きないよう、インタフェース側の実装でも注意を払う。

しかし、ユーザーが何らかの方法で(例えばcurlなどのコマンドラインツール)Relationshipのデータを操作するようなことも起こり得る。

そのような場合でも、**一意なインデックスを追加していれば、エラーを発生させて重複を防ぐことができる。**

relationshipsテーブルを作成するために、いつものようにデータベースのマイグレーションを行う。

```ruby
$ rails db:migrate
```

####演習

1:id=1のユーザーに対して`user.following.map(&:id)`を実行すると、結果はどのようになるか？

引数で受け取ったid=1にフォローされているユーザー(id: 2,7,10,8)のidをそれぞれ１つずつ返す

```ruby
>> user.following.map(id:1)
2
7
10
8 
```

2:id=2のユーザーに対して`user.following`を実行すると、結果はどうなるか？
また、同じユーザーに対して`user.following.map(&:id)`を実行すると、結果はどのようになるか？

```ruby
>> user.following(id:2)
=> [id:1,name:Michael Hartl,email:mhartl@example.com]
>> user.following.map(id:2)
=> 1
```

###14.1.2 User/Relationshipの関連付け

フォローしているユーザーとフォロワーを実装する前に、UserとRelationshipの関連付けを行う。

1人のユーザーには`has_many`(1対多)のリレーションシップがあり、このリレーションシップは2人のユーザーの間の関係なので、**フォローしているユーザーとフォロワーの両方に属している。(belongs_to)**

マイクロポスト作成の時と同様、下記のようなユーザー関連付けのコードを使って新しいリレーションシップを作成する。

```ruby
user.active_relationships.build(followed_id: ...) #user.active_relationshipsをデータモデルとして引数で受け取った値と関連付けて、カラムを生成する
```

この時点では、User/Micropostの関連付けのモデルのようにはならない。

１つ目の違いとして、以前、ユーザーとマイクロポストの関連付けをした時は

```ruby
class User < ApplicationRecord
  has_many :microposts

end
```

このように書いた。

引数の`:microposts`シンボルから、Railsはこれに対応するMicropostモデルを探し出し、見つけることができた。

しかし、今回のケースで同じように書くと

```ruby
has_many :active_relationships
```

となってしまい、ActiveRelationshipモデルを探してしまうので、
相互にフォローユーザーを繋ぐRelationshipモデルを見つけることができない。

このため、今回のケースではRailsに探して欲しいモデルのクラス名を明示的に伝える必要がある。

２つ目の違いは、先ほどの逆のケースについて。

以前はMicropostモデルで

```ruby
class Micropost < ApplicationRecord
  belongs_to :user


end
```

このように書いた。

micropostsテーブルにはuser_id属性があるので、
これを辿って対応するユーザーを特定できた。

DBの２つのテーブルを繋ぐとき、このようなidは外部キー(foreign key)と呼ぶ。

すなわち、Userモデルに繋げる外部キーが、Micropostモデルのuser_id属性ということ。

この外部キーの名前を使って、Railsは関連付けの推測をしている。

具体的には、Railsはデフォルトでは外部キーの名前を<class>_idといったパターンとして理解し、
<class>に当たる部分からクラス名(正確には小文字に変換されたクラス名)を推測する。

```ruby
class Micropost < ApplicationRecord
  belongs_to :user     #Micropostはmicropostモデルのuser_id属性が外部キーと自動で推測する)
```

ただし、マイクロポストではユーザーを例として扱ったが、
今回のケースでは
**フォローしているユーザーを`follower_id`という外部キーを持って特定しなくてはならない**

また、followerというクラス名は存在しないので、ここでもRailsに正しいクラス名を伝える必要が発生する。

先ほどの説明をコードにまとめると、UserとRelationshipの関連付けは以下のようになる。

```rb:user.rb
class User < ApplicationRecord
  # 関連付け
  has_many :microposts, dependent: :destroy
  has_many :active_relationships, class_name:     "Relationship",
                                  foreign_key:    "follower_id",
                                  dependent:      :destroy
```

明示的にclass名や外部キー、destroyも追加している。
(ユーザーを削除したら、ユーザーのリレーションシップも同時に削除される必要があるため)

```rb:relationship.rb
class Relationship < ApplicationRecord
  # 1対1の関連付け
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
end
```

なお、followerの関連付けはまだ使わない。
(書いておくことで構造の理解の手助けになる)


`user.rb`と`relationship.rb`で定義した関連付けにより、13.1で以前紹介したような多くのメソッドが使えるようになった。


出典：[表 14.1: ユーザーと能動的関係の関連付けによって使えるようになったメソッドのまとめ](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)

これらのメソッドを使えば、フォロワーを返したり、フォローしているユーザーを返したりできる。


####演習

1:コンソールを開き、上記表のcreateメソッドを使って、ActiveRelationshipを作ってみる。
DB上に2人以上のユーザーを用意し、最初のユーザーが2人目のユーザーをフォローしている状態を作ってみる。

```ruby
>> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2019-01-31 11:25:14", updated_at: "2019-01-31 11:25:14", password_digest: "$2a$10$ufvz2x2ljsYgknbfQaQrNOF5uG5PP.1YP2jIXom1qCU...", remember_digest: nil, admin: true, activation_digest: "$2a$10$YtXwZx1hETpK66tpv23VJO7a47hav3sFWdwHIpfQDLy...", activated: true, activated_at: "2019-01-31 11:25:14", reset_digest: nil, reset_sent_at: nil>
>> user_second = User.second
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 1]]
=> #<User id: 2, name: "Jordyn Heaney", email: "example-1@railstutorial.org", created_at: "2019-01-31 11:25:14", updated_at: "2019-01-31 11:25:14", password_digest: "$2a$10$3IGSHyPf/ofme0Fump8NF.4kP13rVb9UmiSRnNJkiLt...", remember_digest: nil, admin: false, activation_digest: "$2a$10$vJENgJrXHkTu3.d8/Bpz8OKUK.AJUW1objabSVuoWHE...", activated: true, activated_at: "2019-01-31 11:25:14", reset_digest: nil, reset_sent_at: nil>

>> user.active_relationships.create(followed_id: user_second.id)
   (0.1ms)  begin transaction
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  SQL (2.1ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 1], ["followed_id", 2], ["created_at", "2019-02-04 18:18:22.015668"], ["updated_at", "2019-02-04 18:18:22.015668"]]
   (6.2ms)  commit transaction
=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2019-02-04 18:18:22", updated_at: "2019-02-04 18:18:22">
```

2:active_relationships.followerとactive_relationships.followedの値がそれぞれ正しいことを確認。

フォローしてるのが1で、フォローされてるのが2だと確認できる。

```ruby
=> #<Relationship id: 1, follower_id: 1, followed_id: 2, created_at: "2019-02-04 18:18:22", updated_at: "2019-02-04 18:18:22">
```

###14.1.3 Relationshipのバリデーション

ここでRelationshipモデルの検証を追加して完全なものにしておく。

テストコードとアプリケーションコードを作って実装していく。

ただし、User用のfixtureファイルと同じように、生成されたRelationship用のfixtureでは、
**マイグレーションで制約させた一意性を満たすことができない。**

このままだと正しくテストを行えないので、今の時点では、
生成されたRelationship用のfixtureファイルを空にしておく。

```yml:fixtures/relationships.yml
# 空にする
```

早速、簡単なテストとバリデーションを記入する。

```rb: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 follwer_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
```

```rb:relationship.rb
class Relationship < ApplicationRecord
  # 1対1の関連付け
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"
  validates :follower_id, presence: true
  validates :followed_id, presence: true
end
```

これでテストはパスする。

```ruby
$ rails t
```

####演習

1:Relationshipモデルのvalidatesをコメントアウトしてもテストが成功することを確認。

```rb:relationship.rb
  # validates :follower_id, presence: true
  # validates :followed_id, presence: true
end

$ rails t
3 tests, 3 assertions, 0 failures, 0 errors, 0 skips
```

テストが成功する理由は、Rails5だと初期の時点でバリデーションが掛かってるから。


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

Relationshipの関連付けの核心`following`と`followers`に取りかかる。

今回は`has_many through`を使う。

1人のユーザーにはいくつもの「フォローする」「フォローされる」といった関係性がある。
この関係性を**多対多**と呼ぶ。

デフォルトの`has_many through`という関連付けでは、
Railsはモデル名(単数形)に対応する外部キーを探す。

```ruby
has_many :followeds, through: :active_relationships
```

Railsは`followeds`というシンボルを見て、
これを`followed`単数形に変え、
`relationships`テーブルの`followed_id`を使って対象のユーザーを取得してくる。

しかし、
`user.followeds`という使い方は英語としては不適切。

代わりに、`user.following`という名前を使う。

そのためには、Railsのデフォルトを上書きする必要がある。
ここでは`:source`パラメーターを使って、`following配列の元はfollowed idの集合である`
ということを明示的にRailsに伝える。

```rb:user.rb
class User < ApplicationRecord
  # 関連付け
  has_many :microposts, dependent: :destroy
  # 1対多の関連付け
  has_many :active_relationships, class_name:     "Relationship",
                                  foreign_key:    "follower_id",
                                  dependent:      :destroy
  has_many :following,  through: :active_relationships, source: :followed
```

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

例えば、`include?`メソッドを使ってフォローしているユーザーの集合を調べてみたり、
関連付けを通してオブジェクトを探しだせるようになる。

```ruby
user.following.include?(other_user)
user.following.find(other_user)
```

`following`で取得したオブジェクトは、配列の様に要素を追加したり削除したりすることができる。

```ruby
user.following << other_user
user.following.delete(other_user)
```

<<演算子で配列の最後に追記することができる。

followingメソッドで配列の様に扱えるだけでも便利だが、
Railsは単純な配列ではなく、もっと賢くこの集合を扱っている。例えば次のようなコードでは

```ruby
following.include?(other_user)
```

フォローしている全てのユーザーをDBから取得し、その集合に対して`include?`メソッドを時実行しているように見えるが、実際はDBの中で直接比較をするように配慮している。(other_userがいるかどうかの比較を行なっている)

なお、次のようなコードでは

```ruby
user.microposts.count
```

DBの中で合計を計算した方が高速になる点に注意する。

次に、followingで取得した場合をより簡単に取り扱うために、
`follow`や`unfollow`といった便利メソッドを追加する。

これらのメソッドは、例えば`user.follow(other_user)`といった具合に使う。

さらに、これに関連する`following?`論理値メソッドも追加し、あるユーザーが誰かをフォローしているかどうかを確認できるようにする。

今回は、こういったメソッドはテストから先に書いていく。
と言うのも、Webインターフェイスなどで便利メソッドを使うのはまだ先なので、すぐに使える場面がなく、実装した手応えを得にくいから。

一方で、Usermモデルに対するテストは書くのは簡単かつ今すぐできるので、
先に書いていく。

具体的には、

- `following?`メソッドであるユーザーをまだフォロしていないことを確認
- `follow`メソッドを使ってそのユーザーをフォローできたことを確認
- `unfollow`メソッドでフォロー解除できたことを確認


といった具合でテストしていく。

```rb:user_test.rb
  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
```

`following`による関連付けを使って`follow`、`unfollow`、`following?`メソッドを実装していく。

このとき、可能な限り`self`を省略している点に注目。

```rb:user.rb
  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  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
```

上記コードを追加することで、テストはパスする。

```ruby
13 tests, 19 assertions, 0 failures, 0 errors, 0 skips
```

####演習

1:コンソールを開き、`user_test.rb`のコードを順々に実行してみる。

```ruby
>> michael = User.find(3)
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
=> #<User id: 3, name: "Brittany Schiller", email: "example-2@railstutorial.org", created_at: "2019-01-31 11:25:15", updated_at: "2019-01-31 11:25:15", password_digest: "$2a$10$/KeYm3kd5PfnTaPWl.o/q.yf4I.Q5iXW7K3oSqywWb0...", remember_digest: nil, admin: false, activation_digest: "$2a$10$QctyRqieo7GHcwqke8DSZOm/bbSlBeJ/66VLUF6eukO...", activated: true, activated_at: "2019-01-31 11:25:14", reset_digest: nil, reset_sent_at: nil>
>> archer = User.find(4)
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 4], ["LIMIT", 1]]
=> #<User id: 4, name: "Zula Wilkinson", email: "example-3@railstutorial.org", created_at: "2019-01-31 11:25:15", updated_at: "2019-01-31 11:25:15", password_digest: "$2a$10$16RQ.clWpUqPfhNsAvTJmekbHJiU9fNmjpJ8Ar7FYEX...", remember_digest: nil, admin: false, activation_digest: "$2a$10$TwjgTo5YKF7QJKiT1aKJ2eliFXquMvkNXLwBvqRh/jg...", activated: true, activated_at: "2019-01-31 11:25:15", reset_digest: nil, reset_sent_at: nil>
>> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 3], ["id", 4], ["LIMIT", 1]]
=> false
>> michael.follow(archer)
   (0.1ms)  begin transaction
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 3], ["LIMIT", 1]]
  SQL (5.7ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 3], ["followed_id", 4], ["created_at", "2019-02-05 16:36:58.158969"], ["updated_at", "2019-02-05 16:36:58.158969"]]
   (9.8ms)  commit transaction
  User Load (0.2ms)  SELECT  "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? LIMIT ?  [["follower_id", 3], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 4, name: "Zula Wilkinson", email: "example-3@railstutorial.org", created_at: "2019-01-31 11:25:15", updated_at: "2019-01-31 11:25:15", password_digest: "$2a$10$16RQ.clWpUqPfhNsAvTJmekbHJiU9fNmjpJ8Ar7FYEX...", remember_digest: nil, admin: false, activation_digest: "$2a$10$TwjgTo5YKF7QJKiT1aKJ2eliFXquMvkNXLwBvqRh/jg...", activated: true, activated_at: "2019-01-31 11:25:15", reset_digest: nil, reset_sent_at: nil>]>
>> michael.following?(archer)
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 3], ["id", 4], ["LIMIT", 1]]
=> true
>> michael.unfollow(archer)
  Relationship Load (0.3ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 3], ["followed_id", 4], ["LIMIT", 1]]
   (0.1ms)  begin transaction
  SQL (3.3ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 2]]
   (10.6ms)  commit transaction
=> #<Relationship id: 2, follower_id: 3, followed_id: 4, created_at: "2019-02-05 16:36:58", updated_at: "2019-02-05 16:36:58">
>> michael.following?(archer)
  User Exists (0.3ms)  SELECT  1 AS one FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ? AND "users"."id" = ? LIMIT ?  [["follower_id", 3], ["id", 4], ["LIMIT", 1]]
=> false
```

2:先ほどの演習の各コマンド実行時の結果を見返してみて、実際にはどんなSQLが出力されたのか確認してみる。

上記で確認できる。

###14.1.5 フォロワー

リレーションシップに`user.followers`メソッドを追加する。

`user.following`はフォローしている人数
`user.followers`はフォローされてる人数(フォロワー)である。

フォロワーの配列を展開するために必要な情報は、relationshipsテーブルに既にあり、
`active_relationships`テーブルを再利用することで出来る。

実際、`follower_id`と`followed_id`を入れ替えるだけで、
フォロワーについてもフォローする場合と全く同じ活用が出来る。

データモデルは以下。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/e9503d1d-6ded-0c91-3b1a-2c7888647f6d.png)

出典：[図 14.9: Relationshipモデルのカラムを入れ替えて作った、フォロワーのモデル](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)


要は、`active_relationships`を`passive_relationships`に入れ替えて、
`followed_id`と`follower_id`を入れ替えるだけ。

上記のデータモデルの実装を`user.rb`にhas_manyを使って行う。

```rb:user.rb
class User < ApplicationRecord
  # 関連付け
  has_many :microposts, dependent: :destroy
  # 1対多の関連付け
  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
```

一点、上記で注意すべき箇所は次の様に**参照先(followers)**を指定するための
`:source`キーを省略してもよかった点。

```ruby
has_many :followers, through: :passive_relationships
```

これは、`:followers`属性の場合、
Railsが`followers`を単数形にして自動的に外部キー`follower_id`を探してくれるから。

ただ、必要がないが`has_many :following`との類似性を強調させるために書いている。

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

テストコードは以下の通り。ちなみに`following?`と対照的な`followed_by?`メソッドを定義してもよかったが、
サンプルアプリケーションで実際に使う場面がなかったのでこのメソッドは省略している。

```rb:user_test.rb
  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
```

↑archerのフォロワーにmichaelは含まれているかどうかテストしている。

上記のテストは実際には多くの処理が正しく動いていなければパスしない。
つまり、受動的関係に対するテストは実装の影響を受けやすい。

この時点で、全てのテストはパスする。

```ruby
13 tests, 20 assertions, 0 failures, 0 errors, 0 skips
```

####演習

1:コンソールで、何人かのユーザーが最初のユーザーをフォローしている状況作ってみる。
最初のユーザーを`user`とすると、`user.followers.map(&:id)`のidの値はどのようになっているか？

```ruby
>> user = User.first
>> user_second = User.second
>> user.followers.map(&:id)
" = ?  [["followed_id", 1]]
=> [2, 3]
```

2:`user.followers.count`の実行結果が、先ほどフォローさせたユーザー数と一致していることを確認。

```ruby
>> user.followers.count
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 2
```

3:`user.followers.count`を実行した結果、出力されるSQL文はどのような内容になっているか？
また、`user.followers.to_a.count`の実行結果と違っている箇所はあるか？
100万人ユーザーがフォロワーにいた場合はどうなるか？

```ruby
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
>> user.followers.to_a.count
=> 2
```

フォロワーが100万人いたらそのまま100万と言う数値が返されるが配列を生成する為、時間が掛かるしDBにも負担が掛かる。

##14.2 FollowのWebインターフェイス

これまでやや複雑なデータモデリングの技術を駆使して実装した。

次は、モックアップで示したようにフォロー/フォロー解除の基本的なインターフェイスを実装する。
また、フォローしているユーザーと、フォロワーにそれぞれ表示用のページを作成する。

後に、ユーザーのステータスフィードを追加して、サンプルアプリケーションを完成させる。

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

前章と同じように、サンプルデータを自動生成する`rails db:seed`を使って、DBにサンプルデータを登録できるとやや便利。

先にサンプルデータを自動生成出来るようにしておけば、Webページの見た目のデザインから先に取り掛かることができ、バックエンド機能の実装を後に回すことが出来る。

リレーションシップのサンプルデータを生成するためのコードをseedに書いていく。
ここでは、最初のユーザーにユーザー3からユーザー51をフォローさせ、それから逆にユーザー4からユーザー41に最初のユーザーをフォローさせる。

```rb:seed.rb
$ rails db:migrate:reset
$ rails db:seed
```

####演習

1:コンソールを開き、`User.first.followers.count`の結果がリスト14.14で期待している結果と合致していることを確認。

```ruby
>> User.first.followers.count
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."follower_id" WHERE "relationships"."followed_id" = ?  [["followed_id", 1]]
=> 38
```

2:`User.first.following.count`の結果も合致していることを確認。

```ruby
>> User.first.following.count
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.3ms)  SELECT COUNT(*) FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ?  [["follower_id", 1]]
=> 49
```

###14.2.2 統計と[Follow]フォーム

これでサンプルユーザに、フォローしているユーザーとフォロワーができました。
プロフィールページとHomeページを更新して、これを反映する。

最初に、プロフィールページとHomeページに、
**フォローしているユーザーとフォロワーの統計情報を表示するためのパーシャルを作成する。**

次に、フォローしているユーザーの一覧(following)と
フォロワーの一覧(followers)を表示する専用のページを作成する。

Twitterの慣習にしたがってフォロー数の単位には`following`を使い、
例えば`50 following`といった具合に表示する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/471b7db8-2153-055b-da3e-1b8992ee5f43.png)

出典：[図 14.10: 統計情報パーシャルのモックアップ](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)


上記の統計情報には、現在のユーザーがフォローしている人数と、
現在のフォロワーの人数が表示されている。

それぞれの表示はリンクになっており、専用の表示ページに移動できる。

これらのリンクはダミーテキスト`#`を使って無効にしていた。
しかし、ルーティングについての知識もだいぶ増えてきたので、今回は実装することにする。

実際のページ作成は後にルーティングは今実装する。

このコードでは、`resources`ブロックの内側で`:member`メソッドが使っている。
これは初登場のメソッドだが、まずはどんな動作するのか推測してみる。

```rb: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'
  post    '/signup',  to: 'users#create'
  get     '/login',   to: 'sessions#new'
  post    '/login',   to: 'sessions#create'
  delete  '/logout',  to: 'sessions#destroy'
  resources :users do                                                           # usersリソースをRESTfullな構造にするためのコード。
    member do
      get :following, :followers
    end
  end
                                                
  resources :account_activations, only: [:edit]                                 # editアクションのみaccount_activationsリソースを適用
  resources :password_resets,     only: [:new, :create, :edit, :update]         # password再設定用のリソースを適用
  resources :microposts,          only: [:create, :destroy]                     # micropostsリソースをcreateとdestroyアクションにのみ適用
end
```

この場合のURLは`/users/1/following`や`/users/1/followers`
のようになるのではないかと推測。

また、どちらもデータを表示するページなので、適切なHTTPメソッドはGETリクエストになる。

したがって、getメソッドを使って適切なレスポンスを返す。

ちなみに、memberメソッドを使うとユーザーidが含まれているURLを扱うようになる。

idを指定せずに全てのメンバーを表示するには、次のように`collection`メソッドを使う。

```ruby
resources :users do
  collection do
    get :tigers
  end
end
```

このコードは`/users/tiggers`というURLに応答する。
生成されるルーティングテーブルは以下。

この表で示したフォロー用とフォロワー用の名前付きルートを、今後の実装で使う。


HTTPリクエスト	URL	アクション	名前付きルート
GET	/users/1/following	following	following_user_path(1)
GET	/users/1/followers	followers	followers_user_path(1)

出典：[表 14.2: カスタムルールで提供するリスト 14.15のRESTfulルート](https://railstutorial.jp/chapters/following_users?version=5.1#cha-following_users)


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

```erb:_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ページの両方に表示されるので、
最初の行では、次のコードで現在のユーザーを取得する。

```ruby
<% @user ||= current_user %>
```

@userがnilでない場合(つまりプロフィールページ)は何もせず、
nilの場合には@userにcurrent_userに代入するコードである。

その後、フォローしているユーザーの人数を、次のように関連付けを使って計算する。

```ruby
@user.following.count
```

これはフォロワーについても同様。

```ruby
@user.microposts.count
```

なお、今回も以前と同様に、Railsは高速化のためにDB内で合計を計算している点に注意。

一部の要素で、次のようにCSS idを指定していることにも注目。

```ruby
<strong id="following" class="stat">
</strong>
```

こうしておくと、Ajaxを実装するときに便利です。
そこでは、一意のidを指定してページ要素にアクセスしている。

これで統計情報パーシャルが出来上がる。Homeページにこの統計情報を表示するには、以下のようにすると良い。


```erb:home.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>
```

統計情報にスタイルを与えるために、SCSSを追加する。

変更の結果、Homeページは以下のようにする。

```scss: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;
}
```

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/b44e74b9-3615-b322-55b9-40ca90eed3b0.png)

出典：[図 14.11: Homeページにフォロー関連の統計情報を表示する](https://railstutorial.jp/chapters/following_users?version=5.1#sec-stats_and_a_follow_form)

この後すぐ、プロフィールにも統計情報パーシャルを表示するが、
今のうちに[Follow]/[Unfollow]ボタン用のパーシャルを作成する。

```erb:_follow_form.html.erb
<!--現在のユーザーがURLのユーザーとは違う場合-->
<% unless current_user?(@user) %>
  <div id="follow_form">
    <% if current_user.following?(@user) %>
      <%= render 'unfollow' %>
    <% else %>
      <%= render 'follow' %>
    <% end %>
  </div>
<% end %>
```

このコードは、followとunfollowのパーシャルに作業を振っているだけ。
(urlのユーザーをログインユーザーがフォローしていればunfollow,フォローしていなければfollowをレンダリング)

パーシャルでは、Relationshipsリソース用の新しいルーティングが必要。
これを、Micropostsリソースの例に従って作成する。

```rb: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'
  post    '/signup',  to: 'users#create'
  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 :users                                                              # usersリソースをRESTfullな構造にするためのコード。
  resources :account_activations, only: [:edit]                                 # editアクションのみaccount_activationsリソースを適用
  resources :password_resets,     only: [:new, :create, :edit, :update]         # password再設定用のリソースを適用
  resources :microposts,          only: [:create, :destroy]                     # micropostsリソースをcreateとdestroyアクションにのみ適用
  resources :relationships,       only: [:create, :destroy]                     # 
end
```

フォロー/フォロー解除用のパーシャルも書く。

```erb:_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 %>
```

```erb:_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 %>
```

これら２つのフォームでは、いずれも`form_for`を使ってRelationshipモデルオブジェクトを操作している。

これらの２つのフォームの主な違いは、フォローフォームでは新しいリレーションシップを作成するのに対し、
アンフォローフォームでは既存のリレーションシップを見つけ出すという点。

すなわち、前者はPOSTリクエストをRelationshipsコントローラに送信してリレーションシップをcreate(作成)し、
後者はDELETEリクエストを送信してリレーションシップをdestroy(削除)するということ。

最終的に、このフォロー/アンフォローフォームにはボタンしかないことが理解できる。

しかし、それでもフォローフォームでは`followed_id`をコントローラに送信する必要がある。
これを行うために、`hidden_field_tag`メソッドを使う。

このメソッドは、次のフォーム用HTMLを生成する。

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

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

これでパーシャルとしてフォロー用フォームをプロフィールページに表示できるようになった。

showビューに表示用のhtmlを書く。

```erb:show.html.erb
        </section>
        <section class="stats">
            <%= render 'shared/stats' %>
        </section>
    </aside>
    <div class="col-md-8">
        <%= render 'follow_form' if logged_in? %>
        <% if @user.microposts.any? %>
```

プロフィールには、それぞれ[Follow][Unfollow]ボタンが表示される。

![スクリーンショット 2019-02-07 15.56.13.png](https://qiita-image-store.s3.amazonaws.com/0/244639/3a7ca406-31c0-f8ce-c601-7f6b43ffd7fc.png)

これらのボタンを実装するには、二通りの方法がある。
**１つは標準的な方法、もう１つはAjaxを使う方法。**

でもその前に、フォローしているユーザーとフォロワーを表示するページをそれぞれ作成してHTMLインターフェイスを完成させる。

####演習

1:ブラウザから`/users/2`にアクセスし、フォローボタンが表示されていることを確認する。
同様に、/users/5ではUnfollow]ボタンが表示されているはず。

さて、`/users/1`にアクセスすると、どのような結果が表示されるか？

users/1はログインユーザーなのでボタンが消える。

![スクリーンショット 2019-02-07 16.08.47.png](https://qiita-image-store.s3.amazonaws.com/0/244639/427a5ca4-931c-326b-3ced-e7571d93f553.png)

2:ブラウザからHomeページとプロフィールページを表示してみて、統計情報が正しく表示されているか確認。

確認済み。

3:Homeページに表示されている統計情報に対してテストを書いてみる。
同様にして、プロフィールページにもテストを追加してみる。

```rb:site_layout_test.rb
  test "count relationships" do
    log_in_as(@user)
    get root_path
    assert_match @user.active_relationships.count.to_s, response.body
    assert_match @user.passive_relationships.count.to_s, response.body
  end
```

```rb:users_profile_test.rb
    end
    assert_select @user.microposts.count
    assert_match @user.active_relationships.to_s, response.body
    assert_match @user.passive_relationships.to_s, response.body
  end
```

テストがパスしたのでOK。

###14.2.3 [Following][Followers]ページ

フォローしているユーザーを表示するページと、フォロワーを表示するページは、
いずれも**プロフィールページとユーザー一覧ページを合わせたような作りになるという点**で似ている。

どちらにもフォローの統計情報などのユーザー情報を表示するサイドバーと、ユーザーのリストがある。

さらに、サイドバーには小さめのユーザープロフィール画像のリンクを格子状に並べて表示する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/00d26c70-88f5-5429-b5c0-5d2d2d6e0b5f.png)

出典：[図 14.14: フォローしているユーザー用ページのモックアップ](https://railstutorial.jp/chapters/following_users?version=5.1#sec-stats_and_a_follow_form)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/eb1bac98-ad8b-3a7a-86d0-7a6fda819ade.png)

出典：[図 14.15: ユーザーのフォロワー用ページのモックアップ
](https://railstutorial.jp/chapters/following_users?version=5.1#sec-stats_and_a_follow_form)

ここでの最初の作業は、フォローしているユーザーのリンクとフォロワーのリンクを動くようにすること。

Twitterに倣って、どちらのページでもユーザーのログインを要求するようにする。

そこで前回のアクセス制御と同様に、まずはテストから書いていく。

今回使うテストは以下の通り。

**上記コードでは`following/followers`の名前付きルートを使っている点に注意。**


```rb:users_controller_test.rb
  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
```

この実装には１つだけトリッキーな部分がある。
それは**Usersコントローラに２つの新しいアクションを追加する必要があるということ。**

これはroutesで定義した２つのルーティングに基づいており、これらはそれぞれ`following`および`followers`と呼ぶ必要がある。

それぞれのアクションでは、タイトルを認定し、ユーザーを検索し、@user.followingまたは`@user.follower`sからデータを取り出し、ページネーションを行なって、ページを出力する必要がある。

```rb:users_controller.rb
  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
```

これまで見てきたように、
Railsは慣習に従って、アクションに対応するビューを暗黙的に呼び出す。

例えば、`show`アクションの最後で`show.html.erb`を呼び出す、といった具合。

一方で、上記のいずれのアクションも`render`を明示的に呼び出し、`show_follow`という同じビューを出力している。
したがって、作成が必要なビューはこれ１つ。

renderで呼び出しているビューが同じである理由は、このERBはどちらの場合でもほぼ同じであり、
１つのファイルで両方の場合をカバーできるから。

```erb: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>
```

`users_controller`では、

- followingアクションで`following`を通して`show_follow`ビューを呼び出し、
- followersアクションでは`followers`を通して`show_follow`ビューを呼び出す。

この時、上記コードでは現在のユーザーを一切使っていないので、
他のユーザーのフォロワー一覧ページもうまく動く。

![スクリーンショット 2019-02-07 21.00.33.png](https://qiita-image-store.s3.amazonaws.com/0/244639/44d49b22-ee83-98c3-2926-93cce721f21d.png)

![スクリーンショット 2019-02-07 21.01.00.png](https://qiita-image-store.s3.amazonaws.com/0/244639/61375a21-773b-120b-8b13-caa3db56cdc3.png)

![スクリーンショット 2019-02-07 21.01.47.png](https://qiita-image-store.s3.amazonaws.com/0/244639/7ec63b35-0772-e5b4-69cb-bce5c28ebfd9.png)

beforeフィルターを既に実装しているため、テストはパスする。

```ruby
12 tests, 21 assertions, 0 failures, 0 errors, 0 skips
```

次に、`show_follow`の描画結果を確認するため、統合テストを書いていく。
ただし、今回は基本的なテストだけに留めておき、網羅的なテストにはしない。

**これはHTML構造を網羅的にチェックするテストは壊れやすく、生産性を落としかねないから。**

したがって今回は、
**正しい数が表示されているかどうかと、正しいURLが表示されているかどうかの２つのテストを書く。**

いつものように統合テストを生成するところから始める。

```ruby
$ rails g integration_test following
Running via Spring preloader in process 8224
      invoke  test_unit
      create    test/integration/following_test.rb
```

次に、テストデータをいくつか揃える。
リレーションシップ用のfixtureにデータを追加する。

次のように書くことで

```ruby
orange:
  content: "I just ate an orange!"
  created_at: <%= 10.minutes.ago %>
  user: michael
```

ユーザーとマイクロポストは関連付けできる。

ユーザー名を書かずに

```ruby
user: michael
```

ではなく

```ruby
user_id: 1
```

このようなユーザーidを指定しても関連付けできる。
この例を参考に、Relationship用のfixtureにテストデータを追加する。

```yml:relationships.yml
one:
  follower: michael
  followed: lana

two:
  follower: michael
  followed: malory

three:
  follower: lana
  followed: michael

four:
  follower: archer
  followed: michael
```

上記のfixtureでは、

前半の２つでMichaelがLanaとMaloryをフォローし、
後半の２つでLanaとArcherがMichaelをフォローしている。

あとは、正しい数かどうかを確認するために、
`assert_match`メソッドを使ってプロフィール画面のマイクロポスト数をテストする。

さらに、正しいURLかどうかをテストするコードも加えると、以下のようになる。

```rb:following_test.rb
  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
```

上記では

```ruby
assert_not @user.following.empty?
```

このようなコードを書いているが、これは次のコードを確かめる為のテスト。

```ruby
@user.following.each do |user|
  assert_select "a[href=?]", user_path(user)
end
```

つまり、`@user.following`の結果がtrueであれば、上記のブロックが実行できなくなる為、
その場合においてテストが適切なセキュリティモデルを確認できなくなることを防いでいる。

上の変更を加えるとテストが成功する筈。

```ruby
66 tests, 324 assertions, 0 failures, 0 errors, 0 skips
```

####演習

1:ブラウザから`/users/1/followers`と`/users/1/following`を開き、それぞれが適切に表示されていることを確認。
サイドバーにある画像は、リンクとしてうまく機能しているか？

![スクリーンショット 2019-02-07 22.27.08.png](https://qiita-image-store.s3.amazonaws.com/0/244639/b9accbcd-d19c-1628-cfd5-34f91cfb0d46.png)

![スクリーンショット 2019-02-07 22.27.58.png](https://qiita-image-store.s3.amazonaws.com/0/244639/0c2346eb-44e6-07a5-dc69-face0042b354.png)

![スクリーンショット 2019-02-07 22.28.16.png](https://qiita-image-store.s3.amazonaws.com/0/244639/d2754adb-e72d-db7f-a210-0a054e80a26f.png)

OK

2:`following_test`の`assert_select`関連のコードをコメントアウトしてみて、正しくテストが失敗することを確認。

```erb:show_html.erb
          <% @users.each do |user| %>
            <%= #link_to gravatar_for(user, size: 30), user %>
          <% end %>
$ rails t
            app/controllers/users_controller.rb:61:in `followers'
            test/integration/following_test.rb:20:in `block in <class:FollowingTest>'

 app/controllers/users_controller.rb:54:in `following'
            test/integration/following_test.rb:11:in `block in <class:FollowingTest>'

  66/66: [=================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.39352s
66 tests, 318 assertions, 0 failures, 2 errors, 0 skips
```

###12.2.4 [Follow]ボタン（基本編）

ビューが整ってきた。
いよいよ[Follow]/[Unfollow]ボタンを動作させる。

フォローとフォロー解除はそれぞれリレーション湿布の作成と削除に対応しているため、まずはRelationshipsコントローラが必要。
いつものようにコントローラを生成させる。

```ruby
$ rails g controller Relationships
```

Relationshipsコントローラのアクションでアクセス制御することはそこまで難しくない。

しかし、前回のアクセス制御の時と同様に最初にテストを書き、それをパスするように実装することでセキュリティモデルを確立させていく。
今回はまず、コントローラのアクションにアクセスする時、ログイン済みのユーザーであるかどうかをチェックする。

もしログインしていなければログインページにリダイレクトされるので、Relationshipのカウントが変わっていないことを確認する。

```rb: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
```

次に、上記のテストをパスさせるために、`logged_in_user`フィルターを
Relationshipsコントローラのアクションに対して追加する。

```rb:relationships_controller.rb
class RelationshipsController < ApplicationController
  before_action :logged_in_user
  
  def create
  end
  
  def destroy
  end
end
```

[Follow]/[Unfollow]ボタンを動作させるためには、フォームから送信されたパラメータを使って、`followed_id`に対応するユーザーを見つけてくる必要がある。

その後、見つけてきたユーザーに対して適切に`follow/unfollow`メソッド（Userモデルで定義した）を使う。

```rb: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
```

上記をみてれば、先ほどのセキュリティ問題が実はそれほど重要なものではないことを理解できる。

もし、ログインしていないユーザーが(curlなどのコマンドラインツールなどを使って）これらのアクションに直接アクセスするようなことがあれば、`current_user`は`nil`になり、
**どちらのメソッドでも2行目で例外が発生する。**

エラーにはなるが、アプリケーションやデータに影響は生じない。

このままでも支障はないが、このような例外には頼らない方がいいので、セキュリティの為のレイヤーを追加した。

これで、フォロー/フォロー解除の機能が完成した。

どのユーザーも、他のユーザーをフォローしたりリフォローしたりできる。
ブラウザ上でボタンをクリックして、確かめてみる。
(振る舞いを検証する統合テストはのちに実装する)

![スクリーンショット 2019-02-08 4.49.01.png](https://qiita-image-store.s3.amazonaws.com/0/244639/82156fef-e0a6-1e20-c969-1b3e9ad804ce.png)

フォローしていないユーザーの画面

![スクリーンショット 2019-02-08 4.49.26.png](https://qiita-image-store.s3.amazonaws.com/0/244639/74808d7e-760c-4b13-a440-e86a20a2e9a4.png)

ユーザーをフォローした結果

####演習

1:ブラウザ上から/users/2のFollow/Unfollow実行して動いているか確認

確認済み。

2:先ほどの演習を終えたら、Railsサーバーのログを見てみる。
フォロー/フォロー解除が実行されると、それぞれどのテンプレートが描画されているか？

```ruby
Started GET "/users/2" for 122.50.45.13 at 2019-02-07 19:55:41 +0000
Cannot render console from 122.50.45.13! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
  Parameters: {"id"=>"2"}
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
```

フォローした場合、`/users/2`のビューが描画されている

```ruby
Started GET "/users/2" for 122.50.45.13 at 2019-02-07 19:56:44 +0000
Cannot render console from 122.50.45.13! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by UsersController#show as HTML
  Parameters: {"id"=>"2"}
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
  Rendering users/show.html.erb within layouts/application
```

フォロー解除した場合、`/users/2`のビューが描画されている。


###14.2.5 [Follow]ボタン(Ajax編)

フォロー関連の機能の実装は完了したが、ステータスフィードに取りかかる前にもう１つだけ機能を洗練させてみる。

先ほどは`Relationships`コントローラの`createアクション`と`destroyアクション`を単に元のプロフィールにリダイレクトしていた。
つまり、

①ユーザーはプロフィールページを最初に表示
②ユーザーをフォロー
③すぐ元のページにリダイレクト

という流れになる。

ユーザーをフォローした後、本当にそのページから離れて元のページに戻らないといけないのか。
答えは否(ハンターハンター風)で、同じページにリダイレクトさせる必要はない。

この問題は、`Ajax`を使えば解決できる。

Ajaxを使うことで、Webページからサーバーに「非同期」でページを遷移させることなくリクエストを送信することができる。

WebフォームにAjaxを採用するのは今や当たり前で、RailsでもAjaxを簡単に実装できるようになっている。

フォロー用とフォロー解除用のパーシャルをこれに沿って更新するのは簡単。

例えば、次のコードがあるとすると

```ruby
form_for
```

上のコードを次のように置き換えるだけ

```ruby
form_for ..., remote: true
```

これだけでRailsは自動的にAjaxを使うようになる。

```erb:_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 %>
```

```erb:_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 %>
```

ERbによって実際に生成されるHTMLはこちら

```ruby
<form action="/relationships/117" class="edit_relationship" data-remote="true"
      id="edit_relationship_117" method="post" >
</form>
```

ここでは、formタグの内部で`data-remote="true"`を設定している。

これは、**JavaScriptによるフォーム操作を許可することをRailsに知らせるためのもの。**
(現在のRailsではHTMLプロパティを使って簡単にAjaxが扱えるようになっている)


フォームの更新が終わったので、今度はこれに対応するRelationshipsコントローラを改造して、
Ajaxリクエストに応答できるようにする。

こういったリクエストの種類によって応答を場合分けする時は、`respond_to`メソッドを使う。

```ruby
respond_to do |format|
  format.html { redirect_to user }
  format.js
end
```
上記のブロック内のコードのうち、いずれかの1行が実行されるという点が重要。

このため、`respond_to`メソッドは、上から順に逐次処理(シリアル)というより、
*:if文を使った分岐処理に近いイメージ**

RelationshipsコントローラでAjaxに対応させるために、`respond_to`メソッドをcreateアクションとdestroyアクションにそれぞれ追加してみる。

この時、ユーザーのローカル変数(user)を@userに変更している点に注目。

これは、`_follow.html.erb`と`_unfollow.html.erb`を実装したことにより、
ビューで変数を使うインスタンス変数が必要になったからである。
(Ajaxによる)

```rb: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リクエストに対応したので、今度はブラウザ側で**JavaScriptが無効になっていた場合**でもうまく動くようにする。

```rb:application.rb
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.1

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration should go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded.
    
    # 認証トークンをremoteフォームに埋め込む
    config.action_view.embed_authenticity_token_in_remote_forms = true
  end
end
```

一方で、JavaScriptが有効になっていても、まだ十分に対応できていない部分がある。

というのも、Ajaxリクエストを受診した場合は、Railsが自動的にアクションと同じ名前を持つ
JavaScript用の埋め込みRubyファイル(create.js.erb destroy.js.erb)などを呼び出す為、これらのファイルを作成する必要がある。

.js.erbでは、JSと埋め込みRubyをミックスして現在のページに対するアクションを実行することができる。

ユーザーをフォローしたときや、フォロー解除した時にプロフィールページを更新するために、
これらのファイルが使われる。

`JS-ERb`ファイルの内部では、DOM(Document Object Model)を使ってページ操作するため、
RailsがjQuery JavaScriptヘルパーを自動的に提供している。

これによりjQueryライブラリの膨大なDOM操作用メソッドが使えるようになるが、
今回使うのはわずか２つ。

まず１つ目は、$とCSS idを使って、DOM要素にアクセスする文法について知る必要がある。
例えば、follow_formの要素をjQueryで操作するには、次のようにアクセスする。

```ruby
$("#follow_form")
```

これはフォームを囲むdivタグであり、フォームそのものではない。

jQueryの文法はCSSの記法から影響を受けており、#シンボルを使ってCSSのidを指定する。

jQueryはCSSと同様、ドット.を使ってCSSクラスを操作できる。

次に必要なメソッドはhtml。
これは、引数の中で指定された要素の内側にあるHTMLを更新する。

例えば、フォロー用フォーム全体を"foobar"という文字列で置き換えたい場合は、次のようなコードになる。

```ruby
$("#follow_form").html("foobar")
```

純粋なJavaScriptと異なり、JS-ERbファイルでは組み込みRuby(ERb)が使える。

`create.js.erb`ファイルでは、フォロー用のフォームをunfollowパーシャルで更新し、フォロワーのカウントを更新するのにERbを使っている。

このコードでは`escape_javascript`メソッドを使っている点に注目。

このメソッドは、
**JavaScriptファイル内にHTMLを挿入する時に実行結果をエスケープするために必要。**

```erb:create.js.erb
$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");
$("#followers").html('<%= @user.followers.count %>');
``` 

各行の末尾にセミコロン;があることに注目。
(これは1950年代中頃に開発されたALGOLまで遡るらしい)

`destroy.js.erb`ファイルの方も同様です。

```erb:create.js.erb
$("#follow_form").html("<%= escape_javascript(render(`users/unfollow`)) %>");
$("#followers").html('<%= @user.followers.count %>');
```

```erb:destroy.js.erb
$("#follow_form").html("<%= escape_javascript(render(`users/follow`)) %>");
$("#followers").html('<%= @user.followers.count %>');
```

これらのコードにより、プロフィールページを更新させずにフォローとフォロー解除できるようになった筈。

####演習

1:ブラウザから`/users/2`にアクセスし、うまく動いているかどうか確認。

確認済み。

2:先ほどの演習で確認が終わったら、Railsサーバーのログを閲覧し、フォロー/フォロー解除を実行した直後のテンプレートがどうなっているか確認。

```ruby
   (0.0ms)  begin transaction
  CACHE User Load (0.0ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  SQL (2.2ms)  INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["follower_id", 1], ["followed_id", 2], ["created_at", "2019-02-08 00:24:36.494125"], ["updated_at", "2019-02-08 00:24:36.494125"]]
   (8.0ms)  commit transaction
  Rendering relationships/create.js.erb
  Relationship Load (0.1ms)  SELECT  "relationships".* FROM "relationships" WHERE "relationships"."follower_id" = ? AND "relationships"."followed_id" = ? LIMIT ?  [["follower_id", 1], ["followed_id", 2], ["LIMIT", 1]]
  Rendered users/_unfollow.html.erb (2.0ms)

   (0.1ms)  begin transaction
  SQL (2.8ms)  DELETE FROM "relationships" WHERE "relationships"."id" = ?  [["id", 100]]
   (8.5ms)  commit transaction
  Rendering relationships/destroy.js.erb
  Rendered users/_follow.html.erb (1.2ms)
```

きちんと`js.erb`ファイルがレンダリングされている。

###14.2.6 フォローをテストする

フォローボタンが動くようになったので、バグを検知する為のシンプルなテストを書いていく。

ユーザーのフォローに対するテストでは、`/relationships`に対してPOSTリクエストを送り、
フォローされたユーザーが1人増えたことをチェックする。

具体的なコードは

```ruby
assert_difference '@user.following.count', 1 do
  post relationships_path, params: { followed_id: @other.id }
end
```

これは標準的なフォローに対するテスト。
ただ、Ajax版もやり方はだいたい同じ。

Ajaxのテストでは、xhr :trueオプションを使うようにするだけ。

```ruby
assert_difference '@user.following.count', 1 do
  post relationships_path, params: { followed_id: @other.id }, xhr: true
end
```

ここで使っているxhr(XlHttpRequest)というオプションをtrueにすると
Ajaxでリクエストを発行するよに変わる。

したがって、`respond_to`では、JavaScriptに対応した行が実行されるようになる。

また、ユーザーをフォロー解除する時も構造は殆ど同じで、postメソッドをdeleteメソッドに置き換えてテストする。

つまり、そのユーザーのidとリレーションシップのidを使ってDELETEリクエストを送信し、フォローしている数が１つ減ることを確認する。

したがって、実際に加えるテストは

```ruby
assert_difference '@user.following.count', -1 do
  delete relationship_path(relationship)
end
```

上の従来通りのテストと、下のAjax用のテストの２つになる。

```ruby
assert_difference '@user.following.count', -1 do
  delete relationship_path(relationship), xhr: true
end
```

これらのテストをまとめた結果

```rb:following_test.rb
require 'test_helper'

class FollowingTest < ActionDispatch::IntegrationTest

  def setup
    @user  = users(:michael)
    @other = users(:archer)
    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
  
  test "should follow a user 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
```

この時点でテストはパスする。

####演習

1:`relationships_controller.rb`の`respond_to`ブロック内の各行を順にコメントアウトしていき、テストが正しくエラーを検知できるかどうか確認。

```rb:relationships_controller.rb
    respond_to do |format|
      # format.html { redirect_to @user }
      format.js
    end
```

```ruby
ERROR["test_should_follow_a_user_standard_way", FollowingTest, 1.5789846269981354]
 test_should_follow_a_user_standard_way#FollowingTest (1.58s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: ActionController::UnknownFormat
            app/controllers/relationships_controller.rb:7:in `create'
            test/integration/following_test.rb:31:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:30:in `block in <class:FollowingTest>'

ERROR["test_should_unfollow_a_user_the_standard_way", FollowingTest, 1.6338956370018423]
 test_should_unfollow_a_user_the_standard_way#FollowingTest (1.63s)
ActionController::UnknownFormat:         ActionController::UnknownFormat: ActionController::UnknownFormat
            app/controllers/relationships_controller.rb:16:in `destroy'
            test/integration/following_test.rb:45:in `block (2 levels) in <class:FollowingTest>'
            test/integration/following_test.rb:44:in `block in <class:FollowingTest>'

  72/72: [=================================================================================================================] 100% Time: 00:00:02, Time: 00:00:02

Finished in 2.13223s
72 tests, 334 assertions, 0 failures, 2 errors, 0 skips
```

2:`xhr: true`がある行のうち、片方のみを削除するとどういった結果になるか？
このとき発生する問題の原因と、なぜ先ほどの演習で確認したテストがこの問題を検知できたのかを感がてみる。

```rb:following_test.rb
  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
```

```ruby
 FAIL["test_should_follow_a_user_with_Ajax", FollowingTest, 1.2568579829967348]
 test_should_follow_a_user_with_Ajax#FollowingTest (1.26s)
        "@user.following.count" didn't change by 1.
        Expected: 3
          Actual: 2
        test/integration/following_test.rb:36:in `block in <class:FollowingTest>'
```

Ajaxを用いたフォローで、postリクエストを送信していない為、フォロー数が変化せずテストが失敗する。

##14.3 ステータスフィード

ステータスフィードの実装では、現在のユーザーにフォローされているユーザーのマイクロポストの配列を作成し、
現在のユーザー自身のマイクロポストと合わせて表示する。

このセクションを通して、複雑さを増したフィードの実装に進んでいく。

これを実現するためには、RailsとRubyの高度な機能の他に、SQLプログラミングの技術も必要。

ステータスフィードの最終形のモックアップがこれ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/c4eb9228-1c75-602e-42ca-a0e3fe859376.png)

出典：[図 14.21: ステータスフィード付きのHomeページのモックアップ](https://railstutorial.jp/chapters/following_users?version=5.1#sec-stats_and_a_follow_form)

###14.3.1 動機と計画

ステータスフィードの基本的なアイデアはシンプル。


以下の図の矢印で示されているように、この目的は、現在のユーザーによってフォローされているユーザーに対応するユーザーidを持つマイクロポストを取り出し、同時に現在のユーザー自身のマイクロポストも一緒に取り出すこと。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/244639/5e2e138b-9619-1fb2-533f-29186a6f62a0.png)

出典:[図 14.22: id 1のユーザーがid 2、7、8、10をフォローしているときのフィード
](https://railstutorial.jp/chapters/following_users?version=5.1#sec-stats_and_a_follow_form)

どのようにフィードを実装するのかはまだ明確ではないが、テストについてはやや明確そうなので、
まずはテストから書いていく。


このテストで重要なことは、以下の３つの条件を満たすこと。

- フォローしているユーザーのマイクロポストがフィードに含まれている
- 自分自身のマイクロポストもフィードに含まれている
- フォローしていないユーザーのマイクロポストがフィードに含まれていない


まずは、MichaelがLanaをフォローしていて、Archerをフォローしていないという状況を作ってみる。

この状況のMichaelのフィードでは、Lanaと自分自身の投稿が見えていて、Archerの投稿は見えないことになる。

先ほどの３つの条件をアサーションに変換して、Userモデルにfeedメソッドがあることに注意しながら、
更新したUserモデルに対するテストを書いていく。

```rb:user_test.rb
  test "feed should have the right posts" do
    michael = users(:michael)
    archer  = users(:archer)
    lana    = users(:lana)
    # フォローしているユーザーの投稿を確認
    lana.microposts.each do |post_following|
      assert michael.feed.include?(post_following)
    end
    # 自分自身の投稿を確認
    michael.microposts.each do |post_self|
      assert michael.feed.include?(post_self)
    end
    # フォローしていないユーザーの投稿を確認
    archer.microposts.each do |post_unfollowed|
      assert_not michael.feed.include?(post_unfollowed)
    end
  end
```

feedメソッドはまだ定義していないのでテストは失敗する。

```ruby
$ rails t
```

####演習

1:マイクロポストのidが正しく並んでいると仮定(昇順ソート)して、データセットで`user.feed.map($:id)`を実行すると、どのような結果が表示されるか？考える。

```ruby
user.feed.map($:id)
=>[1,2,7,8,10]
```

このように、引数として受け取った自分のidと、フォローしているidが組み合わさって表示される。


###14.3.2 フィードを初めて実装する

ステータスフィードに対する要件定義は、先ほどのテストで明確になったので、
早速フィードの実装に着手する。

最終的なフィードの実装はやや込み入っているため、細かい部品を１つずつ確かめながら導入していく。

最初に、このフィードで必要なクエリについて考える。
ここで必要なのは、`microposts`テーブルから、
**あるユーザーがフォローしているユーザーに対応するidを持つマイクロポストを全て選択すること。**

このクエリを模式的に書くと

```
SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>
```

上記のコードを書く際に、SQLが`IN`というキーワードをサポートしていることを前提にしている。
(Railsではサポートされている)

このキーワードを使うことで、idの集合を内包(setinclusion)に対してテストを行える。


13章のプロトフィードでは、上のような選択を行うために
Active Recordの`where`メソッドを使っていることを思い出す。

この時に選択すべき対象はシンプルで、
現在のユーザーに対応するユーザーidを持つマイクロポストを選択すればよかった。

```ruby
Micropost.where("user_id = ?", id)
```

今回必要になる選択は、上よりも少し複雑で、例えば次のような形になる。

```ruby
Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
```

これらの条件から、フォローされているユーザーに対応するidの配列が必要であることがわかってきた。

これを行う方法の１つは、Rubyのmapメソッドを使うこと。

このメソッドはすべての「列挙可能」なオブジェクト
(配列やハッシュなど、要素の集合で構成されたあらゆるオブジェクト)
で使える。

なお、このメソッドは四章でも出てきた。
他の例題として、mapメソッドを使って配列を文字列に変換すると、以下のようになる。

```ruby
$ rails console
>> [1,2,3,4].map { |i| i.to_s }
=> ["1","2","3","4"]
```

上記に示したような状況では、各要素に対して同じメソッドが実行される。

これは非常によく使われる方法であり、次のようにアンバサンド(&)と、メソッドに対応するシンボルを使った短縮表記が使える。
この短縮表記であれば、変数iを使わずに済む。

```ruby
>> [1,2,3,4].map(&:to_s)
=> ["1","2","3","4"]
```

この結果に対してjoinメソッドを使うと、idの集合をカンマ区切りの文字列として繋げることができる。

```ruby
>> [1,2,3,4].map(&:to_s).join(', ')
=> "1,2,3,4"
```

上記のコードを使えば、`user.following`にある各要素のidを呼び出し、フォローしているユーザーのidを配列として扱うことができる。

例えばDBの最初のユーザーに対して実行すると、次のような結果になる。

```ruby
>> User.first.following.map(&:id)
  User Load (0.9ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
  User Load (1.0ms)  SELECT "users".* FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ?  [["follower_id", 1]]
=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
```

実際、この手法は実に便利なので、Active Recordでは次のようなメソッドも用意されている。

```ruby
>> User.first.following.ids
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.5ms)  SELECT "users"."id" FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ?  [["follower_id", 1]]
=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
>> 
```

この`following_ids`メソッドは、`has_many :following`の関連付けをした時に、
Active Recordが自動生成したもの。

これにより、user.followingコレクション対応するidを得るためには、
関連付けの名前の末尾に`_ids`を付け足すだけで済む。

結果として、フォローしているユーザーidの文字列は次のようにして取得することができる。

```ruby
>> User.first.following_ids.join(', ')
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
   (0.2ms)  SELECT "users"."id" FROM "users" INNER JOIN "relationships" ON "users"."id" = "relationships"."followed_id" WHERE "relationships"."follower_id" = ?  [["follower_id", 1]]
=> "3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51"
```

ただ、実際のSQL文字列に挿入するときは、このように記述する必要はない。

実は、?を挿入すると自動的にこのあたりの面倒を見てくれる。

さらに、DBに依存する一部の非互換性まで解消してくれる。
つまり、ここでは`following_ids`メソッドをそのまま使えば良いだけ。

```ruby
Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
```

というコードが無事に動いた。

作成したコードはこれ

```rb:user.rb
  # パスワード再設定の期限が切れている場合はtrueを返す
  def password_reset_expired?
    reset_sent_at < 2.hours.ago
  end
  
  # ユーザーのステータスフィードを渡す
  def feed
    Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
  end
  
  # ユーザーをフォローする
  def follow(other_user)
    following << other_user
  end
```

####演習

1:Userモデルにおいて、現在のユーザー自身の投稿を含めないようにするにはどうすれば良いか？
また、そのような変更を加えると、user_test.rbのど部分のテストが失敗するか？

```ruby
$ user = User.first
$ user.feed
 Micropost Load (0.9ms)  SELECT  "microposts".* FROM "microposts" WHERE (user_id IN (3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51) OR user_id = 1) ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 11]]
```
OR user_id = 1にて、自分自身のユーザーidを渡している点に注目。

この渡す為の処理をメソッドから削除する。

```rb:user.rb
  # ユーザーのステータスフィードを渡す
  def feed
    Micropost.where("user_id IN (?) ", following_ids, id)
  end
```

テストの失敗箇所

```ruby
app/models/user.rb:90:in `feed'
test/models/user_test.rb:97:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:96:in `block in <class:UserTest>'
```

2:Userモデルにて、フォローしているユーザーの投稿を含めないように内容にするには？
また、テストの失敗箇所を見てみる。


```rb:user.rb
  # ユーザーのステータスフィードを渡す
  def feed
    Micropost.where("user_id = ?", following_ids, id)
  end
```

```ruby
            app/models/user.rb:90:in `feed'
            test/models/user_test.rb:97:in `block (2 levels) in <class:UserTest>'
            test/models/user_test.rb:96:in `block in <class:UserTest>'
```

3:フォローしていないユーザーの投稿を含めるためにはどうすればいいか？
また、そのような変更を加えると、テストがどう失敗するか？

```rb:user.rb
  # ユーザーのステータスフィードを渡す
  def feed

    Micropost.all
  end
```

全部含めてみる。

```ruby
        Expected true to be nil or false
        test/models/user_test.rb:105:in `block (2 levels) in <class:UserTest>'
        test/models/user_test.rb:104:in `block in <class:UserTest>'
```

falseだけど〜って怒られてる。

###14.3.3 サブセレクト

先ほどのフィードの実装は、投稿されたマイクロポストの数が膨大になった時にうまくスケールしない。

つまり、フォローしているユーザーが5000人程度になると、
**Webサービス全体が遅くなる可能性がある。**


この節では、フォローしているユーザー数に応じてスケールできるように、ステータスフィードを改善していく。

following_idsでフォローしている全てのユーザーをDBに問い合わせし、
さらに、フォローしているユーザーの完全な配列を作るために再度DBに問い合わせしているのは問題である。。

feedメソッドでは、集合に内包されているかどうかだけしかチェックされていない為、この部分はもっと効率的なコードに置き換えられるはず。

また、SQLは本来このような集合の操作に最適化されている。
実際、このような問題は、SQLのサブセレクト(subselect)を使うと解決できる。

まずは、Userモデルのコードを若干修正し、フィードをリファクタリングすることから始める。

```rb:user.rb
  # ユーザーのステータスフィードを渡す
  def feed
    Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
    following_ids: following_ids, user_id: id)
  end
```

上記の実装では、これまでのコード

```ruby
Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
```

次のように置き換えた。

```ruby
Micropost.where("user_id IN (:following_ids) OR user_id = :user_id",
    following_ids: following_ids, user_id: id)
```

疑問符を使った文法も便利だが、
**同じ変数を複数の場所に挿入したい場合は、後者の置き換え後の文法を使う方がより便利。**

上記の説明が示すように、これからSQLクエリにもう１つのuser_idを追加する。
特に、次のコードは

```ruby
following_ids
```

このようなSQLに置き換えることができる。

```ruby
following_ids = "SELECT followed id FROM relationships
                WHERE follower_id = :user_id"
```

このコードをSQLのサブセレクトとして使う。

```ruby
SELECT * FROM microposts
WHERE user_id IN (SELECT followed_id FROM relationships
                  WHERE  follower_id = 1)
      OR user_id = 1
```

つまり、このような階層構造になっている。

- user_id
    - 「ユーザー1がフォローしているユーザー全てを選択する」（サブセレクト）


このように、SELECT文を入れ子の中に内包させる形を「サブセレクト」と言う。

このサブセレクトは、集合のロジックをDBに保存するので、より効率的にデータを取得できる。


上記を元に、効率的なフィードを実装する。

```rb:user.rb
  # ユーザーのステータスフィードを渡す
  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
```

このコードは、Rails+Ruby+SQLのコードが複雑に絡み合っているが、きちんと動作する。

```ruby
14 tests, 58 assertions, 0 failures, 0 errors, 0 skips
```

大規模なWebサービスでは、バックグラウンド処理を使ってフィードを非同期生成するなどのさらなる改善が必要だが、Railsチュートリアルではここまでの改善にしておく。(Railsの入門書なので)


これで、ステータスフィードの実装が完了した。

![スクリーンショット 2019-02-09 3.14.21.png](https://qiita-image-store.s3.amazonaws.com/0/244639/7d20ad53-ecb3-94b4-6e5d-8eb0223f6b39.png)


いつも通り、masterブランチに変更を取り込む。

```ruby
$ rails t
$ git add -A
$ git commit -m "Add user following"
$ git checkout master
$ git merge following-users
```

あとはコードをリポジトリにpushして、本番環境にデプロイ

```ruby
$ git push
$ git push heroku
$ source <(curl -sL https://cdn.learnenough.com/heroku_install)
$ heroku pg:reset DATABASE
$ heroku run rails db:migrate
$ heroku run rails db:seed
```

![スクリーンショット 2019-02-09 4.11.13.png](https://qiita-image-store.s3.amazonaws.com/0/244639/41efe0b2-38c5-637a-ff2a-12d92293d74d.png)

[完成したサンプルAppはこちら](https://yuuki-heroku-sample.herokuapp.com/)

**新規作成アカウントの有効化ができない場合は、以下のid、passでログインしてみてください。**

email:example-2@railstutorial.org
pass:password

####演習

1:Homeページで表示される1ページ目のフィードに対して、統合テストを書いてみる。

```rb:following_test.rb
  test "feed on Home page" do
    get root_path
    @user.feed.paginate(page: 1).each do |micropost|
      assert_match CGI.escapeHTML(micropost.content), response.body
    end
  end
```

2:上記コードでは、期待されるHTMLを`CGI.escapeHTML`メソッドでエスケープしている。
その理由は？
また、試しにエスケープ処理を外して、得られるHTMLの内容を調べてみて、マイクロポストの内容がおかしい点を挙げよ。

```rb:following_test.rb
  test "feed on Home page" do
    get root_path
    @user.feed.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end
```

A:contentをエスケープしている為、CGI.esapeHTMLを加える必要がある。


##14.4 最後に(サンプルアプリケーションで学んだこと)

サンプルappで学んだことをまとめてみる。

- MVCモデル
- テンプレート
- パーシャル
- beforeフィルター
- バリデーション
- コールバック
- データモデルの関連付け(has_many/belongs_to/has_many through)
- セキュリティ
- テスティング
- デプロイ

今後はサンプルAppに

- 返信機能
- メッセージ機能
- フォロワーの通知
- RSSフィード
- RESTAPI
- 検索機能
- いいね機能
- シェア機能

などを加えてオリジナルアプリケーションを完成させていくと良い。


#終わったー

やっとRailsチュートリアルを終えることができました。
長かったですねー。

全部Qiitaにメモったおかげで、流し読みせずに熟読できました。
お陰で、全体を通してWebアプリケーション制作の基礎を理解できたような気がします。

正直、1周目の理解度は30パーセントぐらいでした。
今回の2周目で理解度は80パーセントぐらいまで上がった気がします。


これからはオリジナルのWebアプリケーションを作成していくことで、
今回覚えた内容を自分の物にしていきたいと思います！

みなさんお疲れ様でした！

YUUKI.


#単語集

- has_many through

多対多の関係性を定義する関連付けメソッド。

- source

has_manyに対してパラメータを与えるオプション。
sourceオブションで与えた値は配列の元を表しているので、実際の配列のインデックスは変わらない。

- collection

コレクションルーティングを追加するメソッド。
idを指定せずに全てのメンバーを表示したりできる

![スクリーンショット 2019-02-07 17.05.39.png](https://qiita-image-store.s3.amazonaws.com/0/244639/8c97ed5a-9660-8108-982f-4af0cae5d69d.png)

- Ajax(エイジャックス)

Asynchronous(非同期な) JS + XMLで作られている非同期通信を行いながらインターフェイスの構築を行うプログラミング手法のこと。

サーバーからのレスポンスを待たずにクライアント側の表示を変更させることができる。例えば、Ajaxを使用することで画面遷移せずにHTMLを更新することが可能で、ユーザビリティの向上やサーバー負荷の軽減に繋がる。

Railsでは、`remote: true`で使える。

- respond_to

リクエストの種類によって応答を場合分けするメソッド。
処理内に書いたいずれかの1行が実行されるよう書くことができる。

書き方の例

```ruby
respond_to do |format|
  format.htlm {...}
  format.json {...}
```

- xhr

Xmlhttprequestの略で、Ajax通信かどうかを判定するオプション。
trueを渡すことでAjaxでリクエストを発行出来る。


Ajax

- DOM

Document Object Modelの略で、JSファイル内で別のHTMLファイルなどを読み込む時の役割のこと。

- following_ids

followしているユーザーのidをそれぞれ文字列に変換して、,で区切る値として返すメソッド。

- サブセレクト

SQLのSELECT文を入れ子にしたものを指す。
入れ子構造にすることで、より効率的にデータを取得できる。