Rails Tutorial 拡張機能の返信機能を作ってみた(その2)：modelの変更

Rails Tutorialをの第14章にある、返信機能を作る件で、前回の続きです。
###コーディングの作業でやることを洗い出す
やる作業をおさらいして洗い出します。
・gitでbranchを作る
・testを作る
　　　model、integration

###modelに列を追加
modelの列の追加はどうやってやるかテキストで探します。
9.1.1でmigrateで列を追加していたことが分かります。


modelでreplyによる表示の有無をどう作るか考えます。改めて仕様を見ます。
@replyは受信者のフィードと送信者のフィードにのみ表示されるようにします。
表示する人で３つに分けて考えます。
1　送信者、2　受信者、3．第3者（送信者でも受信者でもない）

####1 送信者
自分がpostしたmicropostなので、今の機能でも表示されます。

####2 受信者
受信者が送信者をフォローしている場合と、していない場合の2つが考えられます。
受信者が送信者をフォローしている場合は、今の機能でも表示されます。
受信者が送信者をフォローしていない場合は、今の機能では表示されません。
ここに機能を追加する必要があると分かります。

機能は、in_reply_toと自分が等しければ表示するです。
メソッドuser.micrpostsで返すところに変更を加える　と考えたのですが、うまくいかないです。
自分がpostしたわけではないからです。
送信者を選んでその人のmicropostを返すのではないです。誰がpostしたかにかかわらず、
in_reply_toと自分が等しいmicropostを全部表示する機能です。

思いついたこととして、micropostが増えるとパフォーマンスに問題が出てくるリスクが高いです。対策としてindexをつける必要がありそうです。
主キーで検索するのではなく、代替キーで検索するリスクだと理解しました。

どう作るかに戻り、この実装方法なら、送信者をフォローしているかどうかにかかわらず表示してくれるので、その場合分けは不要だと分かりました。

次に、どのメソッドに機能を追加するか考えます。
リスト14.46を見たところ、Userモデルのfeedメソッドがよさそうです。
whereで条件を指定できそうです。

####3 第3者
第3者が受信者のフィード画面を表示すると、replyが表示されません。
上記の仕様で機能を作ると、表示されてしまいます。
なので表示されないような機能を追加する必要があります。
in_reply_toと自分が等しくなければ表示しないようにすればいいです。
user.micrpostsメソッドで返すところに変更を加えればよいと初めは考えました。
受信者のところでfeedメソッドがよさそうと上記のとおり分かったので、同じメソッドがよさそうと直観的に感じました。

####条件となるSELECT文
14.3.2をもう一度読みます。

>micropostsテーブルから、あるユーザー (つまり自分自身) がフォローしているユーザーに対応するidを持つマイクロポストをすべて選択 (select) することです

をもとに考え、micropostsテーブルから、自分が受信者であるreplyをすべて選択 (select) することです。

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

とすればよいと考えます。

リスト14.44は

```
  def feed
    Micropost.where("user_id IN (?) OR user_id = ?　OR　in_reply_to = ?", following_ids, id,id)
  end
```

でよさそうです。

これで仕様は押さえられたと判断し、作っていくことにします。

####ブランチ作成
いつものようにブランチを作成します。

```bash
ubuntu:~/environment/sample_app (master) $ git checkout -b reply-micropost
```

####modelに列を追加

modelに列を追加する方法をテキストから探します。
6.3.1 「ハッシュ化されたパスワード」で列を追加していました。同様に作ります。

```bash
ubuntu:~/environment/sample_app (reply-micropost) $ rails generate migration add_reply_to_microposts in_reply_to:integer
```

```ruby:db/migrate/20201003003147_add_reply_to_microposts.rb
class AddReplyToMicroposts < ActiveRecord::Migration[5.1]
  def change
    add_column :microposts, :in_reply_to, :integer
  end
end
```

####modelのテストを作成
次はmodelのテストを作ります。
13.1.2 Micropostのバリデーション　と同じく進めます。
どのmodelのテストをするのかですが、micropostに列を追加したので、micropostと考えます。

reply の送信者、受信者を踏まえ、replyのmicropostを作ります。

replyをするとfeed画面に表示されるテストは、ingegrationテストで行うことにします。その前にmodelのtestで何をテストするかを考えます。

replyで追加するメソッドの機能をテストする、として、CRUDのうちupdate,deleteはなさそうなので、replyをcreate、read、の2つが必要です。コンソールでやってみます。

```
>> user2.microposts.create!(content: "test2", in_reply_to: 1 )
>> user2.microposts.create!(content: "test3", in_reply_to: 1 )                                                                                 

>> Micropost.where(in_reply_to: 1)                                                                                 
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."in_reply_to" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["in_reply_to", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Micropost id: 304, content: "test3", user_id: 30, created_at: "2020-10-03 02:37:38", updated_at: "2020-10-03 02:37:38", picture: nil, in_reply_to: 1>, #<Micropost id: 303, content: "test2", user_id: 30, created_at: "2020-10-03 02:18:35", updated_at: "2020-10-03 02:18:35", picture: nil, in_reply_to: 1>]>
```

今あるメソッドで十分だとすると、modelでは機能を追加しないのでテストも必要なさそうですが、一応書いてみます。

```ruby:test/models/micropost_test.rb
  test "reply should be returned" do
    @reply_post = @reply_sender.microposts.create!(content: "reply test1", in_reply_to: @user.id)
    assert Micropost.where(in_reply_to: @user.id).include?(@reply_post)
  end
```

####integrationテストを作成

modelのテストが出来たので、次にintegrationテストを作ります。
テスト内容は、replyを1件作り、受信者のfeed画面に表示されることです。

その前に、変更をどこに加えるのか考えます。controllerに変更を加えるので、その結果のテストも作る必要があると考えました。

まずはintegrationテストからやります。
参考になりそうなテストを探し、following_test.rbがよさそうです。
また、postするところは、microposts_interface_test.rbを元にコピーします。


```ruby:test/integration/reply_test.rb　　RED
  test "reply to user " do 　 
    log_in_as(@user)
    content = "@reply #{@other.name} Cum aspermatur"
    post microposts_path, params: { micropost: {content: content }}
    log_in_as(@other)
    get root_path
    assert_not @other.following?(@user)
    #get root_path
    assert_match content, response.body
    #assert_match content, response.body
  end
```


####feedを変更
replyを表示するようにfeedに変更を加えます。

まずテストデータを作ります。fixtureにデータを追加します。

```ruby:test/fixtures/microposts.yml
tonton:
  content: "@reply malory tonton is the name of the panda."
  created_at: <%= Time.zone.now %>
  user: michael
```

feedのテストはどこでやるかですが、userモデルに変更をするのでmodelのテストと考えます。

```ruby:test/models/user_test.rb　RED
 test "feed should have the reply posts" do
    michael = users(:michael)
    malory = users(:malory)
    reply_post = microposts(:tonton)
    assert michael.feed.include?(reply_post)
    assert malory.feed.include?(reply_post)
    puts reply_post.content
  end
end
```

feedを変更します。

```ruby:app/models/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
                     OR in_reply_to = reply_id",
                     user_id: id, 
                     reply_id: id )
  end
```


```
tonton:
  content: "@reply malory tonton is the name of the panda."
  created_at: <%= Time.zone.now %>
  user: michael
  in_reply_to: <%= User.find_by(name: "Malory Archer").id %>
```

####feedを変更したことで、他のテストがエラー
feedを変更したことで、他のテストがエラーになりました。エラーメッセージを見ます。

```bash
ubuntu:~/environment/sample_app (reply-micropost) $ rails test test/models/micropost_test.rb 
Running via Spring preloader in process 2886
Started with run options --seed 405

 FAIL["test_order_should_be_most_recent_first", MicropostTest, 0.5243559590000189]
 test_order_should_be_most_recent_first#MicropostTest (0.52s)
        --- expected
        +++ actual
        @@ -1 +1 @@
        -#<Micropost id: 941832919, content: "Writing a short test", user_id: 762146111, created_at: "2020-10-10 01:35:16", updated_at: "2020-10-10 01:35:17", picture: nil, in_reply_to: nil>
        +#<Micropost id: 981300582, content: "@reply malory tonton is the name of the panda.", user_id: 762146111, created_at: "2020-10-10 01:35:17", updated_at: "2020-10-10 01:35:17", picture: nil, in_reply_to: 659682706>
        test/models/micropost_test.rb:33:in `block in <class:MicropostTest>'

  6/6: [===================================================================================================] 100% Time: 00:00:00, Time: 00:00:00
```

テストデータに問題があるとエラーメッセージに出ています。投稿時刻が最新のテストデータをfixtureにmost_recentとして作っていたのに、tontonがそれより投稿時刻が新しくしてしまっていたのが原因と考えます。投稿時刻を最新にする必要はないので、tontonの投稿時刻を直します。

```ruby:test/fixtures/microposts.yml　変更前
tonton:
  content: "@reply malory tonton is the name of the panda."
  created_at: <%= Time.zone.now %>
  user: michael
```

```ruby:test/fixtures/microposts.yml　変更後
tonton:
  content: "@reply malory tonton is the name of the panda."
  created_at: <%= 2.minutes.ago %>
  user: michael
  in_reply_to: <%= User.find_by(name: "Malory Archer").id %>
```

無事直りました。

####画面で表示して確認

@replyが書いてあるmicropostがページに表示されることを確かめます。
リスト 13.25:「サンプルデータにマイクロポストを追加する」を参考にサンプルトデータを作ります。

```ruby:db/seeds.rb
# reply
sender   = users.first
reciever = users.second
reply_content = "@reply #{receiver.name} reply test"
sender.microposts.create!(content: reply_content, 
                          in_reply_to: receiver.id )

```

rails serverを上げて画面を表示すると、replyのmicropostが表示されていることが確かめられました。

##所要時間

10/2から10/10までの7.0時間です。