【Rails】マイクロポストと画像のアップロード【Rails Tutorial 13章まとめ】

##基本的な画像のアップロード
画像付きのマイクロポストを投稿できるようにする。
画像の投稿フォームと、投稿された画像の表示機能を実装する。

###carrierwaveジェム
画像アップロード機能を追加するcarrierwaveジェムをGemfileに追加する。
加えて、画像をリサイズするmini_magickジェムと、本番環境で画像をアップロードできるfogジェムも追加する。

```rb:Gemfile
source 'https://rubygems.org'

gem 'rails',                   '5.1.6'
gem 'bcrypt',                  '3.1.12'
gem 'faker',                   '1.7.3'
gem 'carrierwave',             '1.2.2'
gem 'mini_magick',             '4.7.0'
gem 'will_paginate',           '3.1.5'
gem 'bootstrap-will_paginate', '1.0.0'
.
.
.
group :production do
  gem 'pg',  '0.20.0'
  gem 'fog', '1.42'
end
```

次に、pictureという名前の画像アップローダーを作成する。

```console
$ rails generate uploader Picture
```

###pictureカラム
Micropostモデルに、画像を格納するためのpictureカラムを追加する。

```console
$ rails generate migration add_picture_to_microposts picture:string
$ rails db:migrate
```

CarrierWaveに画像と関連付けたモデルを伝えるためには、mount_uploaderというメソッドを使う。
このメソッドは、引数に属性名のシンボルと生成されたアップローダーのクラス名を取る。

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

railsサーバーとGuardを再起動して、テストがGREENになるようにする。

###画像投稿フォーム
マイクロポストの投稿フォームにf.file_field :pictureを使って画像の投稿フォームを作成する。

```erb:app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) 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="picture">
    <%= f.file_field :picture %>
  </span>
<% end %>
```

マイクロポスト投稿のStrong Parametersを修正して、picture属性が送信できるようにしておく。

```app/controllers/microposts_controller.rb
  private

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

###画像の表示
これで画像が投稿できるようになったので、image_tagとurlメソッドを使ってマイクロポストに画像を表示する。

```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.picture.url if micropost.picture? %>
  </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>
```
picture?メソッドはCarrierWaveによって生成されるメソッドで、Micropostオブジェクトに画像が入っているかを論理値で返す。

##画像アップロードのテスト
画像が投稿できるようになったので、テストを書く。
テスト用画像はtest/fixturesディレクトリに入れる。
マイクロポスト投稿用のテストに追記していく。

```test/integration/microposts_interface_test.rb
  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'div.pagination'
    assert_select 'input[type="file"]'
    # 無効な送信
    post microposts_path, params: { micropost: { content: "" } }
    assert_select 'div#error_explanation'
    # 有効な送信
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: { micropost:
                                      { content: content,
                                        picture: picture } }
    end
    assert @user.microposts.paginate(page: 1).first.picture?
    follow_redirect!
    assert_match content, response.body
    # 投稿を削除する
    .
    .
    .
  end
```

fixture_file_uploadメソッドで、fixture内のファイルをアップロードする。
第一引数に画像へのパス、第二引数に'ファイル形式/拡張子'をとる（多分）。

##投稿画像のバリデーション
投稿可能な画像ファイルのサイズや種類を制限するために、バリデーションを設定していく。
###画像フォーマットとアップローダーファイル
画像ファイルの種類を制限するために、アップローダーファイルを修正する。

```app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end
```
拡張子のリスト部分はデフォルトではコメントアウトされている。

###画像サイズとvalidateメソッド
アップロードできる画像のサイズは５MB以下としたいので、Micropostモデルにバリデーションを追加する。
ここで、ファイルサイズを制限するバリデーションがRailsのデフォルトに無いため、自分で定義しなければならない。
定義したバリデーションを適用するには、validateメソッド（単数形）を使って、メソッドをシンボルの形で指定する。

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

  private

    # アップロードされた画像のサイズをバリデーションする
    def picture_size
      if picture.size > 5.megabytes
        errors.add(:picture, "should be less than 5MB")
      end
    end
end
```
５MBを超えた場合はerrors.addを使って、Micropostオブジェクトのpicture属性にエラーメッセージを追加している。

まずフォーマットのバリデーションを反映するために、投稿フォームのf.file_fieldにacceptパラメータを付ける。

```erb
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
```
また、jQueryを使って警告メッセージを表示しておく。

```erb:app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) 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="picture">
    <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
  </span>
<% end %>

<script type="text/javascript">
  $('#micropost_picture').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.');
    }
  });
</script>
```

##画像のリサイズ
###ImageMagickとMiniMagickジェム
画像を自動でリサイズするには、ImageMagickというプログラムを使う。
Homebrewでは次のようにインストールする。

```console
$ brew install imagemagick
```

ImageMagickを使うためには、MiniMagickジェムが必要である。
CarrierWaveの導入時に導入していない場合はしておく。

画像を縦４００px、横４００pxに制限するには、アップローダーファイルに次のように記述する。

```app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  process resize_to_limit: [400, 400]

  storage :file

  # アップロードファイルの保存先ディレクトリは上書き可能
  # 下記はデフォルトの保存先  
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # アップロード可能な拡張子のリスト
  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end
```

画像のリサイズ機能を使いしたことでテストがREDになるので、次のように修正する。

```config/initializers/skip_image_resizing.rb
if Rails.env.test?
  CarrierWave.configure do |config|
    config.enable_processing = false
  end
end
```

##本番環境での画像アップロード
AWSを使用するようなので、スキップする。