Railsチュートリアル(第6版)　第13章　アカウントの有効化

#第13章
短いメッセージを投稿できるようにするためのリソース「マイクロポスト」機能を追加していく。

手順として
・Micropostデータモデル作成
・Userモデルと`has_many`および`belong_to`メソッドで関連付け
・結果を処理し表示するための必要なフォームを作る

##Micropostモデル
今回のマイクロポストモデルはテストされて、デフォルトの順序を持つ。また、親であるユーザーが破棄されたら自動的に破棄されるものになる。

トピックブランチを作成しておく。

`$ git checkout -b user-microposts`

###基本的なモデル
Micropostモデルは、マイクロポストの内容を保存する`content`属性と、特定のユーザーとマイクロポストを関連付けるための`user_id`属性を持つ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/1d7eb288-bc65-d90f-b5ef-91a5abf0a233.png)
参照：railsチュートリアル

**String型**と**Text型**
・String型は、255文字まで格納できる
・Text型は、それ以上。また、Text用のテキストエリアを使うので、より自然な投稿フォームになる。将来のことを考えるとこっち。さらに、パフォーマンスでは差は出ないとのこと。

Micropostモデルを生成する

`$ rails generate model Micropost content:text user:references`

これで`ApplicationRecord`を継承したモデルが作られた。

```rb:app/models/micropost.rb
class Micropost < ApplicationRecord
   belongs_to :user
end
```

`user:references`という引数を含めていたため、`belongs_to`というコードが追加されている。
この`references`型を利用している点が、Userモデルとの最大の違いになる。
これを利用すると、自動的にインデックスと外部キー参照付きの`user_id`カラムが追加されて、UserとMicropostを関連付ける下準備をしてくれる。

Userモデルと同様に`created_at`と`updated_at`というカラムが追加されてる。

インデックスが付与されたMicropostのマイグレーション

```rb:db/migrate/[timestamp]_create_microposts.rb
class CreateMicroposts < ActiveRecord::Migration[6.0]
  def change
    create_table :microposts do |t|
      t.text :content
      t.references :user, foreign_key: true

      t.timestamps
    end
    add_index :microposts, [:user_id, :created_at]
  end
end
```

上のコードでは、`user_id`と`created_at`カラムにインデックスが付与されている。これで、`user_id`に関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出しやすくなる。
また、`user_id`と`created_at`の両方を1つの配列に含めている。これで、Active Recordは両方のキーを同時に扱う**複合キーインデックス(Multiple Key Index)**を作成する。

マイグレートする
`$ rails db:migrate`

###Micropostのバリデーション
Micropostモデル単体を動くようにする。

Micropostの初期テストは手順
・`setup`でfixtureのサンプルユーザーと紐だ付けた新しいマイクロポストを作成
・次に、作成したマイクロポストが有効かどうかのチェック
・最後に、あらゆるマイクロポストはユーザーのidを持つべきなので、`user_id`の存在性のバリデーションに対するテストを追加

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

  test "user id should be present" do
    @micropost.user_id = nil
    assert_not @micropost.valid?
  end
end
```

1つ目のテストは、現実に即しているかどうかをテスト(reality check)
2つ目のテストは、`user_id`が存在しているかどうか(nilではないか)

`user_id`に対する存在性のバリデーション

```rb:app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  validates :user_id, presence: true
end
```

次に`content`属性に対するバリデーションの追加。

```rb:test/models/micropost_test.rb
  test "content should be present" do
    @micropost.content = "   "
    assert_not @micropost.valid?
  end

  test "content should be at most 140 characters" do
    @micropost.content = "a" * 141
    assert_not @micropost.valid?
  end
```

`content`属性も存在性と、140文字より長くならないという制限を加える。

マイクロポストにバリデーションを追記する。

```rb:app/models/micropost.rb
class Micropost < ApplicationRecord
  belongs_to :user
  validates :user_id, presence: true
  validates :content, presence: true, length: { maximum: 140 }
end
```

###User/Micropostの関連付け
個々のモデル間で関連付けをしておく。
今回は、それぞれのマイクロポストは一人のユーザーと関連付けられ、それぞれのユーザーは潜在的に複数のマイクロポストと関連付けられている。

MicropostとそのUserは`belongs_to`の関係性
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/130cc752-d5c4-9a8e-cd06-84d6bb91aa4f.png)
参照：railsチュートリアル

UserとそのMicropostは`has_many`の関係性
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/79f5970f-e727-c3a6-ae45-66c276fc4fc4.png)
参照：railsチュートリアル

このような関係を行うことで

| メソッド |　用途 |
|:-----------|:------------|
|micropost.user　|Micropostに紐付いたUserオブジェクトを返す　|
|user.microposts　|Userのマイクロポストの集合をかえす　|
|user.microposts.create(arg)|userに紐付いたマイクロポストを作成する　|
|user.microposts.create!(arg)|userに紐付いたマイクロポストを作成する（失敗時に例外を発生）| 
|user.microposts.build(arg)|userに紐付いた新しいMicropostオブジェクトを返す| 
|user.microposts.find_by(id: 1)|userに紐付いていて、idが1であるマイクロポストを検索する| 

上のメソッドが使えるようになる。

注意点として

```rb
Micropost.create
Micropost.create!
Micropost.new
```

ではなく

```rb
user.microposts.create
user.microposts.create!
user.microposts.build
```

となっている。
紐づいているユーザーを通してマイクロポストを作成することができる。新規のマイクロポストがこの方法で作成される場合、`user_id`は自動的に正しい値に設定される。

最初の書き方↓

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

書き換えると↓

```rb
@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")
```

`build`メソッドはオブジェクトを返すがデータベースには反映されない。
一度関連付けを定義すれば、`@micropost`変数の`user_id`には、関連するユーザーidが自動的に設定される。

UserモデルとMicropostモデルを紐づける。
Micropostは、`belongs_to :user`
Userモデルは、`has_many :microposts`と追加する

```rb:app/models/user.rb
class User < ApplicationRecord
  has_many :microposts
  .
  .
  .
end
```

`setup`メソッドを修正し、正しいテストにする。

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

###マイクロポストを改良する
UserとMicropostを関連付けを改良する。
ユーザーのマイクロポストを特定の順序で取得できるようにしたり、マイクロポストをユーザーに依存させ、ユーザーの削除と同時にマイクロポストも自動的に削除されるようにする。

**デフォルトスコープ**
`user.micropost`メソッドはデフォルト状態では読み出し順序に何も保証がない。
ブログやTwitterの慣習に従い、作成時間の逆順、最も新しいマイクロポストを最初に表示する。
これを実装するには、**default scope**を使う。

このテストは、一見成功しているように思えるが、「アプリケーション側の実装が本当は間違っているのにテストが成功してしまう」トラップがある。
なので、テスト駆動開発で進める。

テスト内容
データベース上のマイクロポストが、fixture内のマイクロポスト(`most_recet`)と同じであるか検証する。

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

fixtureでコメント、作成日、ユーザーへの関連付けを行う。

```yml:test/fixtures/microposts.yml
orange:
  content: "I just ate an orange!"
  created_at: <%= 10.minutes.ago %>
  user: michael

tau_manifesto:
  content: "Check out the @tauday site by @mhartl: https://tauday.com"
  created_at: <%= 3.years.ago %>
  user: michael

cat_video:
  content: "Sad cats are sad: https://youtu.be/PKffm2uI4dk"
  created_at: <%= 2.hours.ago %>
  user: michael

most_recent:
  content: "Writing a short test"
  created_at: <%= Time.zone.now %>
  user: michael
```

fixtureファイル内では日付を更新可能となっている。

続いて、Railsの`default_scope`メソッドを使う。
このメソッドは、データベースから要素を取得した時のデフォルトの順序を指定するメソッドになる。
特定の順序にしたい場合、`default_scope`の引数に`order`を与える。
例えば、`created_at`カラムの順にしたい場合は以下の通り。

`order(:created_at)`

デフォルトの順序が昇順の為、小さい値から大きい値にソートされる。つまり最も古い投稿が最初に来る。
順序を逆にするには、生のSQLを引数に与える。

`order('created_at DESC')`

`DESC`は、SQLの降順(descending)を指す。これで新しい投稿から古い投稿の順に並ぶ。

なんとRails4.0以降は、Rubyの文法でも書けるようになった。

`order(created_at: :desc)`

`default_scope`でマイクロポストを順序付ける

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

上のコードでは、ラムダ式という文法を使っている。
これは、Procやlambda(無名関数)と呼ばれるオブジェクトを作成する文法。
`->`というラムダ式は、ブロックを引数に取り、Procオブジェクトを返す。
このオブジェクトは、`call`メソッドが呼ばれたとき、ブロック内の処理を評価する。

**Dependent: destroy**
マイクロポストに第二の要素を追加する。
サイト管理者はユーザーを破棄する権限を持つため、ユーザーが破棄されたらユーザーのマイクロポストも同時に破棄されるべき。

`has_many`メソッドにオプションを渡してあげればOK

```rb:app/models/user.rb
class User < ApplicationRecord
  has_many :microposts, dependent: :destroy
  .
  .
  .
end
```

`dependent: :destroy`というオプションを使うことで、ユーザーが削除されたら、そのユーザーに紐づいたマイクロポストも一緒に削除される。
持ち主の存在しないマイクロポストがデータベースに取り残される問題を防ぐ。

Userモデルを検証する。

テスト内容
・ユーザーを作成する
・そのユーザーに紐づいたマイクロポストを作成する
・その後、ユーザーを削除してみて、マイクロポストが1つ減っているかどうか確認する

```rb: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.microposts.create!(content: "Lorem ipsum")
    assert_difference 'Micropost.count', -1 do
      @user.destroy
    end
  end
end
```

##マイクロポストを表示する
ユーザープロフィールにマイクロポストを表示させるため、最初に極めて神父rなERbテンプレートを作成する。
次に、サンプルデータ生成タスクにマイクロポストのサンプルを追加し、画面にサンプルデータが表示されるようにする。

###マイクロポストの描画
ユーザーのプロフィール画面(`show.html.erb`)で、そのユーザーのマイクロポストを表示させたり、これまでに投稿した総数も表示させる。

Micropostのコントローラーとビューを作成するために、コントローラを生成する。

`$ rails generate controller Microposts`

`_micropost.html.erb`パーシャルを使って、マイクrポストのコレクションを表示しようとすると以下のようになる。

```erb
<ol class="microposts">
  <%= render @microposts %>
</ol>
```

`ul`タグではなく、順序付きリストの`ol`タグを使っている。
マイクロポストが特定の順序に依存しているため。

対応するパーシャのコード↓

```erb:app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>
</li>
```

`time_ago_in_words`というヘルパーメソッドを使っている。これは、「〇分前に投稿」といった文字列を出力するもの。

`<li id="micropost-<%= micropost.id %>">`
これは、各マイクロポストにCSSのidを割り振っている。将来的に、各マイクロポストを操作したくなった時に役立つ。

ページ分割するため、`will_paginate`メソッドを使う。
`<%= will_paginate @microposts %>`

第10章のユーザー一覧画面のコードでは、`<%= will_paginate %>`という単純なコードだった。

実は、上のコードは引数なしで動作していた。`will_paginate`がUsersコントーラのコンテキストにおいて、`@user`インスタンス変数が存在していることを前提としてたから。このインスタンス変数は、`ActiveRecord::Relation`クラスのインスタンス。
今回の場合は、Usersコントローラのコンテキストからマイクロポストをページネーションしたいため(コンテキストが異なる)、明示的に`@microposts`変数を`will_paginate`に渡す必要がある。
従って、そのようなインスタンス変数をUsersコントローラの`show`アクションで定義しなければならない。

`@microposts`インスタンス変数を`show`アクションに追加する。

```rb:app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def show
    @user = User.find(params[:id])
    @microposts = @user.microposts.paginate(page: params[:page])
  end
  .
  .
  .
end
```
マイクロポストの関連付けを経由し、`micropost`テーブルに到達して、必要なマイクロポストのページを引き出している。

最後に、マイクロポストの投稿数を表示するが、これは`count`メソッドでOK

`user.microposts.count`

関連付けを通して、`count`メソッドを呼び出している。
なんと`count`メソッドでは、データベース上のマイクロポストを全部読みだしてから結果の配列に`length`を呼ぶような、無駄な処理はしていない。
(そりゃぁそうでなければね)

データーベースに代わりに計算してもらい、特定の`user_id`に紐づいたマイクロポストの数をデータベースに問い合わせている。countメソッドよりもさらに高速な`counter cache`も使うことが可能。

プロフィール画面にマイクロポストを表示させる。
`if @user.microposts.any?`を使って、ユーザーのマイクロポストが1つもない場合には空のリストを表示させないようにしている。

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

###マイクロポストのサンプル
すべてのユーザーにマイクロポストを追加すると時間が掛かるので、`take`メソッドを使って最初の6人だけ追加する。

`User.order(:created_at).take(6)`

`order`メソッドを経由することで、最初の6人を明示的に呼び出すようにしている。

1ページの表示限界数(30)を越えさせるのに、それぞれ50個分のマイクロポストを追加する。
また、各投稿内容はFaker gemに`Lorem.sentence`というメソッドを使う。

```rb:db/seeds.rb
# ユーザーの一部を対象にマイクロポストを生成する
users = User.order(:created_at).take(6)
50.times do
  content = Faker::Lorem.sentence(word_count: 5)
  users.each { |user| user.microposts.create!(content: content) }
end
```

開発環境のデータベースで生成

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/082d7bfb-0474-ee32-b690-71bfd3c864ad.png)
参照：railsチュートリアル

CSSを整えて、、

```scss:app/assets/stylesheets/custom.scss
/* microposts */

.microposts {
  list-style: none;
  padding: 0;
  li {
    padding: 10px 0;
    border-top: 1px solid #e8e8e8;
  }
  .user {
    margin-top: 5em;
    padding-top: 0;
  }
  .content {
    display: block;
    margin-left: 60px;
    img {
      display: block;
      padding: 5px 0;
    }
  }
  .timestamp {
    color: $gray-light;
    display: block;
    margin-left: 60px;
  }
  .gravatar {
    float: left;
    margin-right: 10px;
    margin-top: 5px;
  }
}

aside {
  textarea {
    height: 100px;
    margin-bottom: 5px;
  }
}

span.image {
  margin-top: 10px;
  input {
    border: 0;
  }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/a4fa8a0e-80d9-d4f4-fa7d-1d16458df42b.png)

###プロフィール画面のマイクロポストをテスト
プロフィール画面で表示されるマイクロポストに対して、統合テストを書く。
まずは、プロフィール画面用の統合テストを生成する。

```
$ rails generate integration_test users_profile
```

マイクロポスト用のfixtureにいくつかデータを追加

```yml:test/fixtures/microposts.yml
orange:
  content: "I just ate an orange!"
  created_at: <%= 10.minutes.ago %>
  user: michael

tau_manifesto:
  content: "Check out the @tauday site by @mhartl: https://tauday.com"
  created_at: <%= 3.years.ago %>
  user: michael

cat_video:
  content: "Sad cats are sad: https://youtu.be/PKffm2uI4dk"
  created_at: <%= 2.hours.ago %>
  user: michael

most_recent:
  content: "Writing a short test"
  created_at: <%= Time.zone.now %>
  user: michael

<% 30.times do |n| %>
micropost_<%= n %>:
  content: <%= Faker::Lorem.sentence(word_count: 5) %>
  created_at: <%= 42.days.ago %>
  user: michael
<% end %>
```

テスト内容
プロフィール画面にアクセス後
・ページタイトルとユーザー名
・Gravatar
・マイクロポストの投稿数
・ページ分割されたマイクロポスト
という順番でテストする。

```rb:test/integration/users_profile_test.rb
require 'test_helper'

class UsersProfileTest < ActionDispatch::IntegrationTest
  include ApplicationHelper

  def setup
    @user = users(:michael)
  end

  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination'
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end
end
```

第12章と同様に、`response.body`を使っている。これには、そのページの完全なHTMLが含まれている。
従って、そのページのどこかしらにマイクロポストの投稿数があれば、以下のように探してマッチできる。

`assert_match @user.microposts.count.to_s, response.body`

上のは`assert_select`よりも抽象的なメソッド。
特に`assert_select`ではどのHTMLタグを探すか指定しなければならない。
`assert_match`メソッドはその必要がない。

さらに、`assert_select`の引数ではネストした文法を使う。

`assert_select 'h1>img.gravatar'`

`h1`タグの内側にある`grabatar`クラス月の`img`タグがあるかどうかをチェックしてる。

##マイクロポストを操作する
データモデリングとマイクロポスト表示テンプレートが完成したので、続いてWeb経由でそれらを作成するためのインターフェースに取り掛かる。

やること
・ステータスフィード実装
・ユーザーがマイクロポストをWeb経由で破棄できるようにする

従来のRails開発と子なる点が1つある。
Micropostリソースへのインターフェイスは、主にプロフィールページとHomeページのコントローラを経由し実行されるため、Micropostコント―羅には`new`や`edit`アクションは不要。

つまり、`create`や`destroy`があればOK

Micropostsリソースは下記になる。

`resources :microposts,          only: [:create, :destroy]`

Micropostsリソースが提供するRESTfulルート

|HTTPリクエスト|URL|アクション |名前付きルート|
|:-----------|:------------|:------------|:------------|
|POST|/microposts|create|microposts_path|
|DELETE|/microposts/1|destroy|micropost_path(micropost)|

###マイクロポストのアクセス制御
Micropostsリソース開発では、コントローラ内のアクセス制御から始める。
関連付けられたユーザーを通してマイクロポストにアクセスするので、`create`アクションや`destroy`アクションを利用するユーザーはログイン済である必要がある。

ログイン済か調べるテストは、Usersコントローラ用のテストがそのまま使える。
正しいリクエストを各アクションに発行し、マイクロポスト数が変化してないかどうか、リダイレクトされるかどうかを確認する。

```rb:test/controllers/microposts_controller_test.rb
require 'test_helper'

class MicropostsControllerTest < ActionDispatch::IntegrationTest

  def setup
    @micropost = microposts(:orange)
  end

  test "should redirect create when not logged in" do
    assert_no_difference 'Micropost.count' do
      post microposts_path, params: { micropost: { content: "Lorem ipsum" } }
    end
    assert_redirected_to login_url
  end

  test "should redirect destroy when not logged in" do
    assert_no_difference 'Micropost.count' do
      delete micropost_path(@micropost)
    end
    assert_redirected_to login_url
  end
end
```

ここでリファクタリングの必要性あり。
第10章では、beforeフィルタの`logged_in_user`メソッドを使って、ログインを要求した。第10章では、Usersコントローラ内にこのメソッドがあったので、beforeフィルターを指定した。
なので、Micropostsコントローラでも同様に必要になる。
なので、各コントローラーが継承するApplicationコントローラに、このメソッドを移す。

```rb:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  include SessionsHelper

  private

    # ユーザーのログインを確認する
    def logged_in_user
      unless logged_in?
        store_location
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end
end
```

Usersコントローラからは、`logged_in_user`は削除しておこう。

これでMicropostsコントローラーからも`logged_in_user`メソッドを呼び出せるようになった。
これで、`create`アクションや`destroy`アクションに対するアクセス制限が、beforeフィルターで簡単に実装できるようになった。

```rb:app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
  end

  def destroy
  end
end
```

###マイクロポストを作成する
第7章でやった、HTTP POSTリクエストをUsersコントローラの`create`アクションに発行するHTMLフォームを作成することで、ユーザーのサインアップをやった。
今回もこれと似てる。違いとして、別のmicropost/neeページを使う代わりに、ホーム画面にフォームを置くという点。

この節での目標
・ユーザーのログイン状態に応じて、ホーム画面の表示を変更する事

マイクロポストの`create`アクションを作る。

```rb:app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]

  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      render 'static_pages/home'
    end
  end

  def destroy
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end
end
```

ユーザー用アクションと似てるが、違いは新しいマイクロポストを`build`するためにUser/Micropost関連付けを使っているというところ。
`micropost_params`でStrong Parmetersを使っていることで、マイクロポストの`cotent`属性だけがWeb経由で変更可能になっている。

マイクロポスト作成フォーム構築のために、サイト訪問者がログインしてるかどうかに応じ、異なるHTMLを提供するコード

```erb: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="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
<% else %>
  <div class="center jumbotron">
    <h1>Welcome to the Sample App</h1>

    <h2>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>

    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
  </div>

  <%= link_to image_tag("rails.svg", alt: "Rails logo", width: "200px"),
              "https://rubyonrails.org/" %>
<% end %>
```

上のコードを動かすためパーシャルを作る必要がある。

Homeページの新しいサイドバー

```erb:app/views/shared/_user_info.html.erb
<%= link_to gravatar_for(current_user, size: 50), current_user %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>
```

ユーザー情報に、そのユーザーが投稿したマイクロポスト総数が表示される。
`pluralize`メソッドを使って、"1 micropost"や"2 microposts"とするよう調整してる。

マイクロポスト作成フォームを定義する。ユーザー登録フォームに似てる。

```erb:app/views/shared/_micropost_form.html.erb
<%= form_with(model: @micropost, local: true) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
<% end %>
```

フォームを動かすため、2か所変更が必要。
１つは、関連付けを使って以下のように`@micropost`と定義すること

`@micropost = current_user.microposts.build`

```rb:app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
    @micropost = current_user.microposts.build if logged_in?
  end
```

`current_user`メソッドはユーザーがログインしている時しか使えないので、`@micropost`変数もログインしているときのみ定義されるようになる。

２つめは、エラーメッセージのパーシャルを再定義すること。

`<%= render 'shared/error_messages', object: f.object %>`

今回は`@mircropost`変数を使う。フォーム変数`f`を`f.object`とし、関連付けられたオブジェクトにアクセスすることができる。

```rb
form_with(model: @user, local: true) do |f|
```

上のように`f.object`が`@user`となる場合と

```rb
form_with(model: @micropost, local: true) do |f|
```

`f.object`が`@micropost`になる場合がある。

パーシャルにオブジェクトを渡すため、値がオブジェクトで、キーがパーシャルでの変数名と同じハッシュを利用する。
`object: f.object`は`error_messages`パーシャルの中で`object`という変数名を作成してくれるので、この変数名を使ってエラーメッセージを更新すれば良い。

```erb:app/views/shared/_error_messages.html.erb
<% if object.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(object.errors.count, "error") %>.
    </div>
    <ul>
    <% object.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>
```

この時点でテストは失敗する。`error_messages`パーシャルの他の出現場所がヒント。
このパーシャルは、他の場所で使われていたため、ユーザー登録、パスワード再設定、ユーザー編集のそれぞれのビューを更新する必要がある。

各ビューを更新した結果を下記に示す。

```erb:app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user, local: true) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>
      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
```

```erb:app/views/users/edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user, local: true) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>

      <%= f.label :name %>
      <%= f.text_field :name, class: 'form-control' %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Save changes", class: "btn btn-primary" %>
    <% end %>

    <div class="gravatar_edit">
      <%= gravatar_for @user %>
      <a href="https://gravatar.com/emails">change</a>
    </div>
  </div>
</div>
```

```erb:app/views/password_resets/edit.html.erb
<% provide(:title, 'Reset password') %>
<h1>Reset password</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(model: @user, url: password_reset_path(params[:id]),
                    local: true) do |f| %>
      <%= render 'shared/error_messages', object: f.object %>

      <%= hidden_field_tag :email, @user.email %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Update password", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>
```

###フィードの原型
マイクロポスト投稿フォームが動作するようになった。
しかし、現時点では投稿した内容をすぐにみる事ができない。なぜなら、Homeページにまだマイクロポストを表示する部分が実装されないから。

フォームが正しく動作してるかチェックする場合
正しいエントリー投稿→プロフィールページに移動→ポストを表示
になるが、これは面倒だ。

なので、ユーザー自身のポストを含むマイクロポストのフィードがないと不便。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/2f40b0b6-f11b-4a76-1d40-df96c0d9c35a.png)
参照：railsチュートリアル

すべてのユーザーがフィードを持つため、`feed`メソッドはUserモデルで作るのが良い。
フィードの原型では、まずは現在ログインしているユーザーのマイクロポストをすべて取得する。
第14章で完全なフィードを実装するので、今回は`where`メソッドでこれを実現する。
`Micropost`モデルに変更を加える。


```rb:app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  # 試作feedの定義
  # 完全な実装は次章の「ユーザーをフォローする」を参照
  def feed
    Micropost.where("user_id = ?", id)
  end

    private
    .
    .
    .
end
```

上のコードにある疑問符は、セキュリティ上重要だ。

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

疑問符があることで、SQLクエリに代入する前に`id`がエスケープされるため、SQLインジェクション(SQL Injection)と呼ばれる深刻なセキュリティホールを避けられる。
この場合、`id`属性は単なる整数(self.idはユーザーのid)であるため危険でないが、SQL文に変数を代入する場合は常にエスケープしておこう。

サンプルアプリケーションにフィード機能を導入するために、ログインユーザーのフィード用にインスタンス変数`@feed_items`を追加、Homeページにはフィード用のパーシャルを追加する。

先ほどあった下記のコード
`@micropost = current_user.microposts.build if logged_in?`
これが

```rb
 if logged_in?
    @micropost  = current_user.microposts.build
    @feed_items = current_user.feed.paginate(page: params[:page])
  end
```

前置if文に変わる。
(1行の時は後置if文、2行の時は前置if文を使うのがRubyの慣習)

homeアクションにフィードのインスタンス変数を追加する。

```rb:app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
    if logged_in?
      @micropost  = current_user.microposts.build
      @feed_items = current_user.feed.paginate(page: params[:page])
    end
  end
```

ステータスフィードのパーシャル

```rb:app/views/shared/_feed.html.erb
<% if @feed_items.any? %>
  <ol class="microposts">
    <%= render @feed_items %>
  </ol>
  <%= will_paginate @feed_items %>
<% end %>
```

ステータスフィードのパーシャルは、Micropostのパーシャルとは異なる。

`<%= render @feed_items %>`

このとき、`@feed_items`の各要素が`Micropost`クラスを持っていたため、RailsはMicropostのパーシャルを呼び出すことができた。
このように、Railsは対応する名前のパーシャルを、渡されたリソースのディレクトリ内から探しにいくことができる。

あとはいつものようにフィードパーシャルを表示すればHomeページにフィードを追加できる。
この結果はHomeページのフィードとして表示される。

```erb: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="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
    <div class="col-md-8">
      <h3>Micropost Feed</h3>
      <%= render 'shared/feed' %>
    </div>
  </div>
```

しかし、マイクロポストの投稿が失敗すると、Homeページは`@feed_items`インスタンス変数を期待しているため、現状では壊れる。
解決法として、Micropostコントローラの`create`アクションへの送信が失敗した場合に備え、必要なフィード変数をこのブランチで渡しておくこと。

```rb:app/controllers/microposts_controller.rb
  def create
    @micropost = current_user.microposts.build(micropost_params)
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      @feed_items = current_user.feed.paginate(page: params[:page])
      render 'static_pages/home'
    end
  end
```

しかし、この時点ではわざと長いマイクロポストを投稿するとエラーが出る。
ページネーション用のリンクを表示すると、"2"のリンクと"Next"のリンクがどちらも同じ次のページを指している。
`create`アクションはMicropostsコントローラにあるので、このURLは「/microposts?page=2」となります。しかし、これはMicropostsの存在していないindexアクションを開こうとしている。
その結果、どちらのリンクをクリックしてもエラーが発生する。

この問題は、Homeページに対応する`controller`パラメータと`action`パラメータを明示的に`will_paginate`に渡せばOK
例として、`static_pages`コントローラと`home`アクションを渡すと次の通り。

```erb:app/views/shared/_feed.html.erb
<% if @feed_items.any? %>
  <ol class="microposts">
    <%= render @feed_items %>
  </ol>
  <%= will_paginate @feed_items,
                    params: { controller: :static_pages, action: :home } %>
<% end %>
```

###マイクロポストを削除する
削除機能を追加する。
これは、ユーザー削除と同じように"delete"リンクでOK。
今回は自分が投稿したマイクロポストのみ削除リンクが動作するようにする。

まずは、マイクロポストのパーシャルに削除リンクを追加する。

```erb:app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content"><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>
```

次に、Micropostsコントローラの`destroy`アクションを定義する。
ユーザーの実装とほぼ同じ。違いは、`admin_user`フィルターで`@user`変数を使うのではなく、関連付けを使いマイクロポストを見つけるようにする点。
これで、あるユーザーが他のユーザーのマイクロポストを削除しようとすると、自動的に失敗するようになる。

`correct_user`フィルター内で`find`メソッドを呼び出すことで、現在のユーザーが削除対象のマイクロポストを保有してるかどうか確認する。

```rb:app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy
  .
  .
  .
  def destroy
    @micropost.destroy
    flash[:success] = "Micropost deleted"
    redirect_to request.referrer || root_url
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content)
    end

    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end
```

`destroy`メソッドではリダイレクトを使っている。

`request.referrer || root_url`

ここで`request.referrer`というメソッドを使っている。
このメソッドはフレンドリーフォワーディングの`request.url`変数と似ており、一つ前のURLを返す。(今回なら、Homeページ)
なので、マイクロポストがHomeページから削除された場合でもプロフィールページから削除された場合でも、`request.referrer`を使うことによりDELETEリクエストが発行されたページに戻れる。
また、元に戻すURLが見つからなくても`||`演算子で`root_url`をデフォルトに設定してるので問題なし。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/aa4fed28-d2c4-6ff2-2e2e-034a4e6b9883.png)
マイクロポスト作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/08d31415-0fa2-c1e1-2f85-c142486c4090.png)
マイクロポスト削除

###フィード画面のマイクロポストをテストする
Micropostモデルとそのインターフェイスが完成した。
あとは、Micropostsコントローラの認可をチェックするテストと、それらをまとめる統合テストだ。

マイクロポスト用のfixtureに、別々のユーザーに紐づけられたマイクロポストを追加する。

別のユーザーに所属しているマイクロポストを追加

```yml:test/fixtures/microposts.yml
ants:
  content: "Oh, is that what you want? Because that's how you get ants!"
  created_at: <%= 2.years.ago %>
  user: archer

zone:
  content: "Danger zone!"
  created_at: <%= 3.days.ago %>
  user: archer

tone:
  content: "I'm sorry. Your words made sense, but your sarcastic tone did not."
  created_at: <%= 10.minutes.ago %>
  user: lana

van:
  content: "Dude, this van's, like, rolling probable cause."
  created_at: <%= 4.hours.ago %>
  user: lana
```

続いて、自分以外のユーザーのマイクロポストは削除しようとした時、適切にリダイレクトされることを確認する。

```rb:test/controllers/microposts_controller_test.rb
  test "should redirect destroy for wrong micropost" do
    log_in_as(users(:michael))
    micropost = microposts(:ants)
    assert_no_difference 'Micropost.count' do
      delete micropost_path(micropost)
    end
    assert_redirected_to root_url
  end
```

最後に、統合テストを書く。

統合テストの内容
・ログイン
・マイクロポストのページ分割確認
・無効なマイクロポストを投稿
・有効なマイクロポストを投稿
・マイクロポストの削除
・他のユーザーのマイクロポストには[delete]リンクが非表示
という順番でテストする。

統合テストを生成する。

`$ rails generate integration_test microposts_interface`

マイクロポストUIに対する統合テスト

```rb:test/integration/microposts_interface_test.rb
require 'test_helper'

class MicropostsInterfaceTest < ActionDispatch::IntegrationTest

  def setup
    @user = users(:michael)
  end

  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'div.pagination'
    # 無効な送信
    assert_no_difference 'Micropost.count' do
      post microposts_path, params: { micropost: { content: "" } }
    end
    assert_select 'div#error_explanation'
    assert_select 'a[href=?]', '/?page=2'  # 正しいページネーションリンク
    # 有効な送信
    content = "This micropost really ties the room together"
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: { micropost: { content: content } }
    end
    assert_redirected_to root_url
    follow_redirect!
    assert_match content, response.body
    # 投稿を削除する
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end
    # 違うユーザーのプロフィールにアクセス（削除リンクがないことを確認）
    get user_path(users(:archer))
    assert_select 'a', text: 'delete', count: 0
  end
end
```

##マイクロポストの画像投稿
ここでは、画像付きマイクロポスト投稿の機能を実装する。

手順として、まずは開発環境のβ版を実装し、その後いくつかの改善を通して本番環境用の完成版を実装する。

画像アップロード機能を追加するための２つの視覚要素
・1つ目は、画像をアップロードするためのフォーム
・2つ目は、1つ目に投稿された画像そのもの

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/ba8d4728-3a42-676a-116b-6021b5aca950.png)
参照：railsチュートリアル　モックアップ画像

###基本的な画像アップロード
Railsでファイルをアップロードする簡単な方法は、Railsに組み込まれているActive Storage機能を使う事。
Active Storageで画像を簡単に扱えて、画像に関連付けるモデルも自由に指定可能となる。
また、Active Storageは汎用性が高く、平文テキスト、PDFファイルや音声等様々なバイナリファイルも扱える。

Active Storageインストールコマンド

`$ rails active_storage:install`

上のコマンドで、添付ファイルの保存に用いるデータモデルを作成するためのデータベースマイグレーションが1つ作られる。

次にマイグレーションを実行

`$ rails db:migrate`

Active Storageの中で最初に知るべきことは、`has_one_attached`メソッドだ。
これは、指定のモデルとアップロードされたファイルを関連付けるのに使う。
下の場合は、`image`を指定してMicropostモデルと関連付けてる。

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

このアプリケーションでは、「マイクロポスト1件につき画像は1件」という設計をしているが、Active Storageは他にも`has_many_attached`オプションも提供してる。文字通り、Active Recordオブジェクト1件につき複数のファイルを添付できるもの。

Homeページに画像をアップロードを追加するのに、マイクロポストフォームに`file_field`タグを含める。

マイクロポストのcreateフォームに画像アップロードを追加

```erb:app/views/shared/_micropost_form.html.erb
<%= form_with(model: @micropost, local: true) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="image">
    <%= f.file_field :image %>
  </span>
<% end %>
```

最後に、Micropostsコントローラを更新し、新たに作成したmicropostオブジェクトに画像を追加できるようにする。
Active Storage APIにはそのための`attach`メソッドが提供されており、これを使う。
具体的にMicropostsコントローラの`create`アクションの中で、アップロードされた画像を`@micropost`オブジェクトにアタッチする。
このアップロード許可のために、`micropost_params`メソッドを更新し、`:image`を許可済み属性リストに追加して、Web経由で更新できるようにする。

```rb:app/controllers/microposts_controller.rb
class MicropostsController < ApplicationController
  before_action :logged_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: :destroy

  def create
    @micropost = current_user.microposts.build(micropost_params)
    @micropost.image.attach(params[:micropost][:image])
    if @micropost.save
      flash[:success] = "Micropost created!"
      redirect_to root_url
    else
      @feed_items = current_user.feed.paginate(page: params[:page])
      render 'static_pages/home'
    end
  end

  def destroy
    @micropost.destroy
    flash[:success] = "Micropost deleted"
    redirect_to request.referrer || root_url
  end

  private

    def micropost_params
      params.require(:micropost).permit(:content, :image)
    end

    def correct_user
      @micropost = current_user.microposts.find_by(id: params[:id])
      redirect_to root_url if @micropost.nil?
    end
end
```

一度画像がアップロードされれば、micropostパーシャルの`image_tag`ヘルパーを用いて、関連付けられた`micropost.image`を描画できる。
また、画像の無いテキストのみのマイクロポストでは画像を表示させないようにするため、`attached?`という論理値を返すメソッドを使っている。

```erb:app/views/microposts/_micropost.html.erb
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    <%= image_tag micropost.image if micropost.image.attached? %>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>
```

###画像の検証
画像アップロード機能を持たせたが、欠点がある。それは、アップロードされた画像に対する制限がないため、もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生する。
この欠点を直すために、画像サイズやフォーマットに対するバリデーションを実装する。

Railsチュートリアル執筆時点では、Active Storageは、こうしたフォーマット機能やバリデーションがネイティブでサポートされていないとのこと。
なので、そのような機能をgemで追加する。

```Gemfile
gem 'active_storage_validations', '0.8.2'
```

いつものように`bundle install`する。

このgemでは、`content_type`を検査する事で画像をバリデーションできる。

```rb
content_type: { in: %w[image/jpeg image/gif image/png],
                message: "must be a valid image format" }
```

上記のコードは、サポートする画像フォーマットに対応する画像MIME typeをチェックしてる。
配列構文`%w[]`がある。

同じく、ファイルサイズも以下のようにバリデーション可能。

```rb
size: { less_than: 5.megabytes,
        message: "should be less than 5MB" }
```

上はtimeヘルパーの時に使った構文と同じで、画像の最大サイズを5MBに制限してる。

これらのバリデーションをまとめた結果をMicropostモデルに追加する。

```rb:app/models/micropost.rb
class Micropost < ApplicationRecord
.
.
.
  validates :image,   content_type: { in: %w[image/jpeg image/gif image/png],
                                      message: "must be a valid image format" },
                      size:         { less_than: 5.megabytes,
                                      message: "should be less than 5MB" }
end
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/3c70b1a2-20f8-53fe-6cbb-2d3f748a0162.png)
参照：railsチュートリアル

先ほどのモデルに追加バリデーションを強化するために、クライアント側でも画像アップロードのサイズやフォーマットをチェックする仕組みを入れる。
まずは、JavaScript(jQuery)にて、ユーザーがアップロードしようとする画像が大きすぎたらアラートを表示するようにする。

jQueryでファイルサイズをチェックする。

```erb:app/views/shared/_micropost_form.html.erb
<script type="text/javascript">
  $("#micropost_image").bind("change", function() {
    var size_in_megabytes = this.files[0].size/1024/1024;
    if (size_in_megabytes > 5) {
      alert("Maximum file size is 5MB. Please choose a smaller file.");
      $("#micropost_image").val("");
    }
  });
</script>
```

最後に、`accept`パラメータを`file_field`入力タグで用いれば、有効なフォーマットでないとアップロードできない事をユーザーに伝えられる。

```erb:app/views/shared/_micropost_form.html.erb
<%= form_with(model: @micropost, local: true) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.text_area :content, placeholder: "Compose new micropost..." %>
  </div>
  <%= f.submit "Post", class: "btn btn-primary" %>
  <span class="image">
    <%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %> 
  </span>
<% end %>
```

`<%= f.file_field :image, accept: "image/jpeg,image/gif,image/png" %>`
このコードは最初に有効な画像フォーマットだけを選択可能にしておき、それ以外のファイルタイプを灰色で表示するもの。

ブラウザ側に色々コードを追加したが、このコードは無効なファイルをアップロードしにくくするだけ。
その気になれば`curl`等でPOSTリクエストを直接発行して、無効なファイルをアップロードできてしまう。
なので、サーバー側のバリデーションは必要なのだ。

###画像のリサイズ
ファイルサイズのバリデーションは実装した。
今度は、画像サイズ(縦横の長さ)に対する制限がない。なので、大きすぎる画像サイズがアップロードされるとレイアウトが崩壊する。
かといって、ユーザーで画像サイズを変更させるのは大変不便だ。
そのため、画像を表示させる前にサイズを変更するようにする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1471120/23dd7ea6-e133-64c6-8a4e-6b4df65f001f.png)
参照：railsチュートリアル

画像を操作するプログラムが必要なので、ImageMagickを使う。

開発環境にインストールする。

`$ sudo apt-get -y install imagemagick`

続いて、画像処理のためにいくつかgemを追加する。
`image_processing`gem、Ruby製ImageMagickプロセッサの`mini_magick`gemが必要だ。

```Gemfile
gem 'image_processing',           '1.9.3'
gem 'mini_magick',                '4.9.5'
```

いつものように`bundle install`する。

これで、Active Storageが提供する`variant`メソッドで変換済画像を作成できるようになる。
特に`resize_to__limit`オプションを用いて、下記のように画像の幅や高さが500ピクセルを越えないように制約をかけとく。

`image.variant(resize_to_limit: [500, 500])`

上記のコードを`display_image`メソッドにおいて利便性を高めよう。

```rb:app/models/micropost.rb
  # 表示用のリサイズ済み画像を返す
  def display_image
    image.variant(resize_to_limit: [500, 500])
  end
```

`display_image`をmicropostパーシャルで使えるようになった。

リサイズ済の`display_image`を使う。

```
<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content">
    <%= micropost.content %>
    <%= image_tag micropost.display_image if micropost.image.attached? %>
  </span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    <% if current_user?(micropost.user) %>
      <%= link_to "delete", micropost, method: :delete,
                                       data: { confirm: "You sure?" } %>
    <% end %>
  </span>
</li>
```

上記のコードに
`<%= image_tag micropost.display_image if micropost.image.attached? %>`
があるが、そこに`display_image`メソッドを使ってる。

`variant`によるリサイズは、`<%= image_tag micropost.display_image if micropost.image.attached? %>`で最初に呼ばれる時にオンデマンドで実行される。以後、結果をキャッシュするので効率が良い。

###本番環境での画像アップロード
画像アップロード機能を実装したが、このままで本番環境に適さない。
本番環境では、ファイルシステムではなくクラウドストレージサービスに画像を保存するようにする。

今回はAWSのS3(Simple Storage Service)を使う。

S3を使うためgemを設定する。

`gem 'aws-sdk-s3',              '1.46.0', require: false`

例のごとく`bundle install`を実行する。

AWSの設定方法は省く。
railsチュートリアルに画像付きで細かく載っているため。

##最後に
Micropostsリソースによって、良い感じにTwitterっぽくなった。
次章は、ユーザーをお互いフォローする仕組みを作っていく。
ユーザー同士のリレーションシップモデリングを学んで、マイクロポストフィードにどんな感じで関連するか学ぶ。