Railsチュートリアル 第13章 ユーザーのマイクロポスト - 基本的な画像アップロード

# 作成中のアプリケーションから、画像アップローダー「CarrierWave」を使えるようにする

画像投稿機能を実装するために必要な画像アップローダーとして、今回は、「CarieerWave」という画像アップローダーを用います。CarrierWaveをRailsアプリケーションで用いるためには、Railsアプリケーション側に`carrierwave`というgemが必要となります。

早速`Gemfile`を更新していきましょう。

```diff: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.6'
  ...略

  group :production do
    gem 'pg',  '0.20.0'
+   gem 'fog', '1.42'
  end

  ...略
```

とりあえず現時点で必要になるのは`carrierwave`のみです。`mini_magickや`fog`というgemは、今後追加していく機能で使用するgemです。

Gemfileを更新したので、当然ながら`bundle install`を実行する必要があります。

```
# bundle install
...略
Fetching gem metadata from https://rubygems.org/........
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies....
...略
Fetching mime-types-data 3.2019.1009
Installing mime-types-data 3.2019.1009
Fetching mime-types 3.3.1
Installing mime-types 3.3.1
Fetching carrierwave 1.2.2
Installing carrierwave 1.2.2
．．．略
Fetching mini_magick 4.7.0
Installing mini_magick 4.7.0
...略
Bundle complete! 29 Gemfile dependencies, 88 gems now installed.
Gems in the group production were not installed.
Bundled gems are installed into `/usr/local/bundle`
```

`bundle install`は特に問題なく完了できたようです。

# Railsのジェネレーターで画像アップローダーを生成する

```
# rails generate uploader Picture
Running via Spring preloader in process 15235
      create  app/uploaders/picture_uploader.rb
```

少なくとも`carrierwave`（または他の画像アップローダー）gemがインストールされていないと、`rails generate uploader`コマンドは正常に完了しません。

# アップロードされた画像をMicropostモデルに関連付けるようにする

## マイクロポストに画像を関連付けるために必要な属性の追加

マイクロポストの画像投稿機能は「画像はマイクロポストに紐付けされる」という実装内容になるため、アップロードされた画像はMicropostモデルに関連付けられるのが自然な実装モデルです。実際には、「RDB上`microposts`テーブルの`picture`カラムに、アップロードされた画像のファイル名を格納する」という形の実装をします。`picture`カラムにはファイル名が入るので、型は`string`となります。

`picture`カラムを追加した新たなマイクロポストのデータモデルは、以下のようになります。

![Micropost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323778/a1e35e89-5db3-38bf-084f-6a4ff6dd23e8.png)

データモデルに変更を加えたので、対応するマイグレーションファイルの生成、ならびに生成したマイグレーションのRDBへの適用が必要となります。なお今回は、生成したマイグレーションファイルの内容に手を加える部分はありません。

```
# rails generate migration add_picture_to_microposts picture:string
Running via Spring preloader in process 15244
      invoke  active_record
      create    db/migrate/20200105225338_add_picture_to_microposts.rb

# rails db:migrate
== [timestamp] AddPictureToMicroposts: migrating ===========================
-- add_column(:microposts, :picture, :string)
   -> 0.0183s
== [timestamp] AddPictureToMicroposts: migrated (0.0199s) ==================
```

## Micropostモデル側でCarrierWaveを使うために必要な実装

画像と関連付けたモデルをCarrierWave側に伝えるためには、モデル側で`mount_uploader`メソッドを呼び出します。`mount_uploader`の第1引数は「属性名を指すシンボル」、第2引数は「アップローダーのクラス名」を取ります。例えば「モデル側の属性名が`picture`、アップローダー名が`PictureUploader`」であれば、`mount_uploader`メソッドは以下のように呼び出されます。

```ruby:
mount_uploader :picture, PictureUploader
```

`app/models/micropost.rb`全体の変更内容は以下のようになります。

```diff: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 test
Running via Spring preloader in process 15277
Started with run options --seed 12165

  61/61: [=================================] 100% Time: 00:00:13, Time: 00:00:13

Finished in 13.04142s
61 tests, 329 assertions, 0 failures, 0 errors, 0 skips
```

# マイクロポスト投稿フォームに画像アップローダーを追加する

Homeページに画像アップローダーを表示させるためには、マイクロポスト投稿フォームに画像アップローダーを追加するのが自然です。このような場合に使うのは`file_field`メソッドとなります。早速`app/views/shared/_micropost_form.html.erb`に画像アップローダーの実装を追加しましょう。

```diff: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 %>
```

# MicropostモデルのWebから更新できる属性に、新たに`picture`を追加する

Webから画像をアップロードしてマイクロポストに紐付けられるようにするためには、Micropostコントローラーで用いているStrong Parameters機能で、Webからの更新を許可するパラメーターに`picture`を追加する必要があります。変更対象のファイルは`app/controllers/microposts_controller.rb`ですね。

```diff:app/controllers/microposts_controller.rb
  class MicropostsController < ApplicationController
    ...略

    private

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

      ...略
  end
```

# マイクロポストの画像表示を追加する

マイクロポストに画像を関連付けても、その画像がフィード画面に表示されないのでは意味がありません。というわけで、Micropostパーシャル側にも「マイクロポストの画像表示」のための新たな実装が必要となります。Micropostパーシャルの実体である`app/views/microposts/_micropost.html.erb`は、以下のように変更していきます。

```diff: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="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?`というメソッドの戻り値によってわかるようになっています。

`picture?`メソッドは、CarrierWaveによって自動的に生成されるメソッドです。メソッド名は、モデル側で定義した画像の属性名をもとにして、CarrierWaveによって自動的に決定されます。