CarrierWaveとMiniMagickでシンプルな画像アップロード機能を実装（リサイズの具体例付き）

![スクリーンショット 2021-08-12 9.38.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89868/fc499218-4bbe-4499-62eb-4c0be00062c2.png)

# 0. はじめに

Railsで画像アップロードといえば`CarrierWave`と`MiniMagick`が鉄板ですね。
数年前、プログラミングスクールに通っていた際にはあまり理解していなかった画像アップロード機能。
超久々に触ったので備忘録として残します🙏

`ActiveStorage`という選択肢もありますが、あまり優位性がなさそうだったので採用を見送りました。
*参考: [ActiveStorage vs CarrierWave](https://qiita.com/w5966qzh/items/510d4c2a3829524b2e64)*


# 1. 作業環境

```yaml
OS:    macOS Big Sur 11.5.1
Ruby:  3.0.2
Rails: 6.1.4
```

# 2. 環境構築

## 2-1. Gemのインストール

`CarrierWave`と`MiniMagick`をGemfileに追加して`bundle install`します。

```ruby:Gemfile
gem 'carrierwave', '~> 2.0'
gem 'mini_magick'
``` 

```shell:terminal
% bundle install
```

## 2-2. Uploaderクラスの作成

Gemがインストールできたら、次は`Uploader`を作成しましょう。
`CarrierWave`の`Uploader`は、画像の保存場所や保存ファイル名、併用するライブラリ等を設定するためのクラスです。

`Uploader`は下記コマンドで自動生成されます。
ちなみに`image`の部分は任意の文字列で問題ありません。
画像を扱うということが分かりやすいように、今回は`image`としてます。

```shell:terminal
% bundle exec rails g uploader image
```

すると`app/uploaders/image_uploader.rb`というファイルが生成されます。
中身はこんな感じ。あとで各種設定をイジるので一旦このまま次に進みましょう。

```ruby:app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url(*args)
  #   # For Rails 3.1+ asset pipeline compatibility:
  #   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
  #
  #   "/images/fallback/" + [version_name, "default.png"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process scale: [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
  # version :thumb do
  #   process resize_to_fit: [50, 50]
  # end

  # Add an allowlist of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  # def extension_allowlist
  #   %w(jpg jpeg gif png)
  # end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end
end
```

## 2-3. 画像カラムにUploaderをマウント

`Uploader`は画像保存に関する設定ファイルです。
このままでは役に立ちません。
画像を保存するためのカラムにマウントする必要があります。

例えば`usersテーブル`に画像を保存するための`photoカラム`があったとします。
その場合は`Userモデル`に下記の設定をします。

```ruby:app/models/user.rb
class User < ApplicationRecord
  mount_uploader :photo, ImageUploader
end
```

## 2-4. MiniMagickの設定

次は`MiniMagick`の設定をします。
`MiniMagick`は、サイズやアスペクト比の変更などに必要な画像処理のためのライブラリです。

設定はとっても簡単。
`app/uploaders/image_uploader.rb`の一部コメントアウトを解除しましょう。

```ruby:app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  include CarrierWave::MiniMagick # <- ここのコメントアウトを外しただけ
```

たったこれだけ。
これで最低限の画像アップロード機能は完成です🙆‍♂️

# 3. Uploaderの設定を変更する

次は`Uploader`の設定を変更していきます。
`app/uploaders/image_uploader.rb`のコードを上から見ていきましょう。

## 3-1. 画像の保存場所

画像の保存場所はローカルストレージかクラウドストレージの2種類です。
実用的な使い方をするのであればクラウドストレージ以外に選択肢はありませんが、本題ではないので今回はローカルストレージにします。

設定場所はココ。
ローカルストレージの場合は`:file`、クラウドストレージの場合は`:fog`にします。

```ruby:app/uploaders/image_uploader.rb
  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog
```

ちなみにクラウドストレージを使用する場合は、これ以外にも設定やGemの追加が必要となります。
*詳しくは[公式ドキュメント](https://github.com/carrierwaveuploader/carrierwave#fog)参照*

## 3-2. 保存ディレクトリ

画像を保存するディレクトリを定義します。
デフォルトではこのようになっています。

```ruby:app/uploaders/image_uploader.rb
  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end
```

このままの設定で画像保存をすると、Railsアプリに`uploads/user/photo/1`のようなディレクトリが自動生成され、その中に画像ファイルが保存されます。
特に問題ないので今回はこのままにして進みます。

## 3-3. 保存サイズとアスペクト比

`MiniMagick`のおかげで様々なサイズやアスペクト比を設定することができます。
設定部分はこちら。

```ruby:app/uploaders/image_uploader.rb
  # Process files as they are uploaded:
  # process scale: [200, 300]
```

一旦ここを下記のように変更します。

```ruby:app/uploaders/image_uploader.rb
  # Process files as they are uploaded:
  process resize_to_fit: [200, 200]
```

この場合はアスペクト比を保ったまま`横200px・縦200px`の大きさに収まるよう画像を保存してくれます。第一引数が横サイズ、第二引数が縦サイズの指定です。

他にも下記のような保存方法があります。

|メソッド|説明|[ 300, 100 ] にした場合<br />※ 画像はクリックして見てね|
|:-|:-|:-:|
|process resize_to_fit: [width, height]|アスペクト比を維持したまま指定サイズに収まるようリサイズする。<br />元画像が指定サイズよりも大きければ小さく、指定サイズよりも小さければ引き伸ばす。|![スクリーンショット 2021-08-12 9.38.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89868/5bf36ff4-9372-31db-65b1-907007d5e2f5.png)|
|process resize_to_limit: [width, height]|アスペクト比を維持したまま指定サイズに収まるようリサイズする。<br />元画像が指定サイズよりも大きければ小さくするが、指定サイズよりも小さければ何もせず。|![スクリーンショット 2021-08-12 9.38.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89868/5bf36ff4-9372-31db-65b1-907007d5e2f5.png)|
|process resize_to_fill: [width, height, gravity='Center']|指定サイズに余白が出来ないように画像を伸縮し、はみ出した部分は切り捨てられる。<br />そのため、元画像のアスペクト比と指定サイズのアスペクト比が異なる場合は必ず画像の一部が切り取られる。<br />`gravity`にて切り取り中心位置を決定する。|![スクリーンショット 2021-08-12 9.38.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89868/1e9f3b60-8c99-9b99-c11d-6846a787a4ee.png)|
|process resize_and_pad: [width, height, background=:transparent, gravity='Center']|指定サイズ内に収まるようリサイズされる。<br />余った余白は`background`で指定した色で埋め尽くされる。<br />元画像をどこに配置するかは`gravity`により決定する。|![スクリーンショット 2021-08-12 9.38.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89868/c01d5607-d726-4700-c997-2677d1d74491.png)<br /><font color="orange">※背景色をオレンジにした場合</font>|

## 3-4. サムネサイズの設定

画像は表示箇所によってサイズを変えたい場合があります。
例えばヘッダーやユーザーの一覧画面でアイコンを表示する場合は、少し小さめなサイズで表示したいですよね？
そんな設定はココで出来ます。

```ruby:app/uploaders/image_uploader.rb
  # Create different versions of your uploaded files:
  # version :thumb do
  #   process resize_to_fit: [50, 50]
  # end
```

`version`の後には好きな名称を指定できます。
そして保存方法は`3-3. 保存サイズとアスペクト比`で説明したものがそのまま使えます。

分かりやすく設定例を挙げておきましょう。

```ruby:app/uploaders/image_uploader.rb
  # Create different versions of your uploaded files:
  version :icon do
    process resize_to_fit: [50, 50]
  end

  version :icon_large do
    process resize_to_fit: [100, 100]
  end
```

このように複数の`version`を定義することができるので必要な分だけ設定しておきましょう。
今回はヘッダーで使うアイコンは小さめに、ユーザー一覧画面で使うアイコンは少し大きめに表示したい場合を想定しています。　

画像それぞれ以下のようなメソッドで表示することが可能です。
（※ログイン中ユーザーを`current_user`とした場合）

```erb:hoge.html.erb
<!-- 通常の画像表示 -->
<%= image_tag current_user.photo_url %>

<!-- 小さめアイコンの表示 -->
<%= image_tag current_user.photo_url(:icon) %>

<!-- 大きめアイコンの画像表示 -->
<%= image_tag current_user.photo_url(:icon_large) %>
```

## 3-5. 許容する拡張子の設定

どんなファイルを受け入れるか設定することができます。
これにより悪意あるスクリプトファイルのアップロードを防ぐことができます。

```ruby:app/uploaders/image_uploader.rb
  # Add an allowlist of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  # def extension_allowlist
  #   %w(jpg jpeg gif png)
  # end
```

通常はコメントアウトを外すだけで十分だと思います。

```ruby:app/uploaders/image_uploader.rb
  # Add an allowlist of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  def extension_allowlist
    %w(jpg jpeg gif png)
  end
```

## 3-6. 保存ファイル名

保存ファイル名を変更したい場合、あると思います。
該当箇所はこちらです。

```ruby:app/uploaders/image_uploader.rb
  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end
```

ファイル名をどのように変更するか、あまりいいイメージが湧かなかったので説明は割愛します😋 笑

# 最後に

以上で基本的な設定は終了です。
あとは使いながら自分の好きな設定に適宜変更してみてください👍

# 参考資料

[CarrierWave](https://github.com/carrierwaveuploader/carrierwave)
[MiniMagick](https://github.com/minimagick/minimagick)
[CarrierWaveを使って、ユーザー画像を設定する。](https://qiita.com/nekotanku/items/5da43600f35eada64eac)
[CarrierWave+MiniMagickで使う、画像リサイズのメソッド](https://qiita.com/wann/items/c6d4c3f17b97bb33936f)