作成中のアプリケーションから、画像アップローダー「CarrierWave」を使えるようにする
画像投稿機能を実装するために必要な画像アップローダーとして、今回は、「CarieerWave」という画像アップローダーを用います。CarrierWaveをRailsアプリケーションで用いるためには、Railsアプリケーション側にcarrierwaveというgemが必要となります。
早速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カラムを追加した新たなマイクロポストのデータモデルは、以下のようになります。
データモデルに変更を加えたので、対応するマイグレーションファイルの生成、ならびに生成したマイグレーションの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メソッドは以下のように呼び出されます。
mount_uploader :picture, PictureUploader
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に画像アップローダーの実装を追加しましょう。
  <%= 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ですね。
  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は、以下のように変更していきます。
  <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によって自動的に決定されます。
