【Railsチュートリアル】第13章 ユーザーのマイクロポスト①

# はじめに
* ユーザーが短いメッセージを投稿する「マイクロポスト」追加
* Micropostデータモデル作成
* UserとMicropostの関連付け
* 結果を処理し表示するために必要なフォームとその部品の作成

# 13.1 Micropostモデル

## 13.1.1 基本的なモデル
* `String`型: テキストフィールドを使う。255文字までは格納可能。
* `Text`型: テキストエリアを使う。言語に応じて投稿の長さを調節することができる。

### 演習 1
RailsコンソールでMicropost.newを実行し、インスタンスを変数micropostに代入してください。その後、user_idに最初のユーザーのidを、contentに "Lorem ipsum" をそれぞれ代入してみてください。この時点では、 micropostオブジェクトのマジックカラム（created_atとupdated_at）には何が入っているでしょうか?

```ruby
>> micropost = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> User.first
  User Load (1.8ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-03-11 02:55:03", updated_at: "2021-03-11 05:26:02", password_digest: [FILTERED], remember_digest: nil, admin: true, activation_digest: "$2a$12$DmeHgps2JUFM85z.knYwau3w9FYaBJmyb52l/Acc6b3...", activated: true, activated_at: "2021-03-11 02:55:03", reset_digest: nil, reset_sent_at: "2021-03-11 05:25:31">
>> micropost.user_id = 1
=> 1
>> micropost.content = "Lorem ipsum"
=> "Lorem ipsum"
>> micropost
=> #<Micropost id: nil, content: "Lorem ipsum", user_id: 1, created_at: nil, updated_at: nil>
```

`created_at: nil`, `updated_at: nil`

### 演習 2
先ほど作ったオブジェクトを使って、micropost.userを実行してみましょう。どのような結果が返ってくるでしょうか? また、micropost.user.nameを実行した場合の結果はどうなるでしょうか?

```ruby
>> micropost.user
  User Load (1.7ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-03-11 02:55:03", updated_at: "2021-03-11 05:26:02", password_digest: [FILTERED], remember_digest: nil, admin: true, activation_digest: "$2a$12$DmeHgps2JUFM85z.knYwau3w9FYaBJmyb52l/Acc6b3...", activated: true, activated_at: "2021-03-11 02:55:03", reset_digest: nil, reset_sent_at: "2021-03-11 05:25:31">

>> micropost.user.name
=> "Example User"
```

### 演習 3
先ほど作ったmicropostオブジェクトをデータベースに保存してみましょう。この時点でもう一度マジックカラムの内容を調べてみましょう。今度はどのような値が入っているでしょうか?

```ruby
>> micropost.save
   (0.1ms)  begin transaction
  Micropost Create (12.8ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2021-03-13 09:05:45.857838"], ["updated_at", "2021-03-13 09:05:45.857838"]]
   (9.1ms)  commit transaction
=> true

>> micropost
=> #<Micropost id: 309, content: "Lorem ipsum", user_id: 1, created_at: "2021-03-13 09:05:45", updated_at: "2021-03-13 09:05:45">
```

`created_at: "2021-03-13 09:05:45"`, `updated_at: "2021-03-13 09:05:45"`

## 13.1.2 Micropostのバリデーション
* 要求される制限を実現するためのバリデーションを追加する

```ruby:test/models/micropost_test.rb
require 'test_helper'

class MicropostTest < ActiveSupport::TestCase

  def setup
    @user = users(:Michael)
    # このコードは慣習的に正しくない
    @micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
  end

  test "should be valid" do
    # 有効化どうかのテスト
    assert @micropost.valid?
      # @micropostが有効ならtrue
  end

  test "user id should be present" do
    # ユーザーIDが存在するかどうかのテスト
    @micropost.user_id = nil
      # @micropost.user_idにnilを代入
    assert_not @micropost.valid?
      # @micropostが無効ならtrue
  end

  test "content should be present" do
    # contentが存在するかどうかのテスト
    @micropost.content = "   "
      # @micropost.contentに空白を代入
    assert_not @micropost.valid?
      # @micropostが無効ならtrue
  end

  test "content should be at most 140 characters" do
    # contentが140文字以内か確認するテスト
    @micropost.content = "a" * 141
      # @micropost.contentに「a」141文字分を代入
    assert_not @micropost.valid?
      # @micropostが無効ならtrue
  end
end
end
```

```ruby:app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
    # userと紐付け: micropostに対しuserは1つ
  validates :user_id, presence: true
    # user_idのバリデーション: 存在したらtrue
  validates :content, presence: true, length: { maximum: 140 }
    # contentのバリデーション: 存在したらtrue, 文字数は最大140文字
end
```

### 演習 1
Railsコンソールを開き、user_idとcontentが空になっているmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

```ruby
>> m = Micropost.new
=> #<Micropost id: nil, content: nil, user_id: nil, created_at: nil, updated_at: nil>
>> m.valid?
=> false

>> m.errors.full_messages
=> ["User must exist", "User can't be blank", "Content can't be blank"]
```

`"User must exist"`: Userは存在する必要があるよ
`"User can't be blank"`: Userの値が空だよ
`"Content can't be blank"`: Contentの値が空だよ

### 演習 2
コンソールを開き、今度はuser_idが空でcontentが141文字以上のmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?

```ruby
>> m.content = "m" * 141
=> "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"
>> m.valid?
=> false
>> m.errors.full_messages
=> ["User must exist", "User can't be blank", "Content is too long (maximum is 140 characters)"]
```

`"User must exist"`: Userは存在する必要があるよ
`"User can't be blank"`: Userの値が空だよ
`"Content is too long (maximum is 140 characters)"`: Contentの値が長いよ(最大140文字にしてね)

## 13.1.3 User/Micropostの関連付け
* モデル間での関連付けを十分考える

* MicropostとそのUserは `belongs_to`（1対1）の関係性がある。
* UserとそのMicropostは `has_many`（1対多）の関係性がある。

Twitterで考えると、1つのTweetに対してそのTweetを投稿しているUserが複数ではおかしい。
逆に、Userは複数のTweetをすることができる（複数のTweetデータを持っている）

```ruby:test/models/micropost_test.rb
@user = users(:michael)
# このコードは慣習的に正しくない
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
```

```ruby:test/models/micropost_test.rb
@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")
```

モデル間を紐付けていると「`誰の`micropostsを作るのか」を直接設定できる。

### 演習 1
データベースにいる最初のユーザーを変数userに代入してください。そのuserオブジェクトを使ってmicropost = user.microposts.create(content: "Lorem ipsum")を実行すると、どのような結果が得られるでしょうか?

```ruby
>> user = User.first
   (4.4ms)  SELECT sqlite_version(*)
  User Load (4.1ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-03-11 02:55:03", updated_at: "2021-03-11 05:26:02", password_digest: [FILTERED], remember_digest: nil, admin: true, activation_digest: "$2a$12$DmeHgps2JUFM85z.knYwau3w9FYaBJmyb52l/Acc6b3...", activated: true, activated_at: "2021-03-11 02:55:03", reset_digest: nil, reset_sent_at: "2021-03-11 05:25:31">
>> micropost = user.microposts.create(content: "Lorem ipsum")
   (0.1ms)  begin transaction
  Micropost Create (13.9ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2021-03-14 13:02:23.133630"], ["updated_at", "2021-03-14 13:02:23.133630"]]
   (9.7ms)  commit transaction
=> #<Micropost id: 310, content: "Lorem ipsum", user_id: 1, created_at: "2021-03-14 13:02:23", updated_at: "2021-03-14 13:02:23">
```


### 演習 2
先ほどの演習課題で、データベース上に新しいマイクロポストが追加されたはずです。user.microposts.find(micropost.id)を実行して、本当に追加されたのかを確かめてみましょう。また、先ほど実行したmicropost.idの部分をmicropostに変更すると、結果はどうなるでしょうか?

```ruby
>> user.microposts.find(micropost.id)
  Micropost Load (1.1ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["user_id", 1], ["id", 310], ["LIMIT", 1]]
=> #<Micropost id: 310, content: "Lorem ipsum", user_id: 1, created_at: "2021-03-14 13:02:23", updated_at: "2021-03-14 13:02:23">

>> user.microposts.find(micropost)
Traceback (most recent call last):
        1: from (irb):4
ArgumentError (You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`.)
```

### 演習 3
user == micropost.userを実行した結果はどうなるでしょうか? また、user.microposts.first == micropost を実行した結果はどうなるでしょうか? それぞれ確認してみてください。

```ruby
>> user == micropost.user
=> true
>> user.microposts.first == micropost
  Micropost Load (1.3ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> true
```

## 13.1.4 マイクロポストを改良する
* ユーザーのマイクロポストを特定の順序で取得できるようにする
* マイクロポストをユーザーに依存させて、ユーザーが削除されたらマイクロポストも自動的に削除されるようにする

### デフォルトのスコープ
* 最も新しいマイクロポストを最初に表示する

```ruby:test/models/micropost_test.rb
require 'test_helper'

class MicropostTest < ActiveSupport::TestCase
  .
  .
  .
  test "order should be most recent first" do
    # 直近のマイクロソフトから順に表示しているか確認するテスト
    assert_equal microposts(:most_recent), Micropost.first
      # データベース上の最初のマイクロポストが、fixture内のマイクロポストと同じか
  end
end
```

```ruby:app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  default_scope -> { order(created_at: :desc) }
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
```

特定の順序にしたい場合は、default_scopeの引数にorderを与える。
デフォルトでは昇順になっているので降順の引数を渡す。

### Dependent: destroy
* サイト管理者にユーザーを破棄する権限を持たせる
* ユーザーが破棄された場合、ユーザーのマイクロポストも破棄されるようにする

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
    # ユーザーが削除されると紐付いたマイクロソフトも削除される
  .
  .
  .
end
```

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

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end
  .
  .
  .
  test "associated microposts should be destroyed" do
    # 紐付いたマイクロソフトが破棄されているか確認するテスト
    @user.save
      # @userをDBに保存
    @user.microposts.create!(content: "Lorem ipsum")
      # contentに"Lorem ipsum"を渡してユーザーに紐付いたマイクロソフトを作成
    assert_difference 'Micropost.count', -1 do
      # マイクロポストが1つ減っているか検証する
      @user.destroy
        # ユーザーを削除する
    end
  end
end
```

### 演習 1
Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。

```ruby

>> Micropost.first.created_at
   (4.1ms)  SELECT sqlite_version(*)
  Micropost Load (7.2ms)  SELECT "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> Sun, 14 Mar 2021 13:02:23 UTC +00:00

>> Micropost.last.created_at
  Micropost Load (1.9ms)  SELECT "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> Thu, 11 Mar 2021 02:56:21 UTC +00:00
```

### 演習 2
Micropost.firstを実行したときに発行されるSQL文はどうなっているでしょうか? 同様にして、Micropost.lastの場合はどうなっているでしょうか? ヒント: それぞれをコンソール上で実行したときに表示される文字列が、SQL文になります。


```ruby:Micropost.first

>> Micropost.first.created_at
   (4.1ms)  SELECT sqlite_version(*)
  Micropost Load (7.2ms)                  # Micropostを読み込む
  SELECT "microposts".* FROM "microposts" # micropostsからすべての行(*)を取得
  ORDER BY "microposts"."created_at"      # micropostsのcreated_atを
  DESC LIMIT ?  [["LIMIT", 1]]            # 降順に並べ直した1つめのデータ
                         
=> Sun, 14 Mar 2021 13:02:23 UTC +00:00
```

```ruby:Micropost.last

>> Micropost.last.created_at
  Micropost Load (1.9ms)                  # Micropostを読み込む
  SELECT "microposts".* FROM "microposts" # micropostsからすべての行(*)を取得
  ORDER BY "microposts"."created_at"      # micropostsのcreated_atを
  ASC LIMIT ?  [["LIMIT", 1]]             # 昇順に並べ直した1つめのデータ

=> Thu, 11 Mar 2021 02:56:21 UTC +00:00
```

### 演習 3
データベース上の最初のユーザーを変数userに代入してください。そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか? 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。

```ruby
# データベース上の最初のユーザーを変数userに代入してください。
>> user = User.first
  User Load (3.3ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-03-11 02:55:03", updated_at: "2021-03-11 05:26:02", password_digest: [FILTERED], remember_digest: nil, admin: true, activation_digest: "$2a$12$DmeHgps2JUFM85z.knYwau3w9FYaBJmyb52l/Acc6b3...", activated: true, activated_at: "2021-03-11 02:55:03", reset_digest: nil, reset_sent_at: "2021-03-11 05:25:31">
>> user
```
```ruby

# そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか?
>> user.microposts.last
  Micropost Load (1.7ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> #<Micropost id: 1, content: "Quia labore nulla beatae totam.", user_id: 1, created_at: "2021-03-11 02:56:21", updated_at: "2021-03-11 02:56:21">
```
```ruby

# 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。
>> user.destroy
```
```ruby

# 削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。
>> Micropost.find(1)
  Micropost Load (1.3ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["id", 1], ["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):10
ActiveRecord::RecordNotFound (Couldn't find Micropost with 'id'=1)
```