画像周りの扱い方


これは何?

Railsアプリケーションを扱うときに、画像周りを扱うのは必須。

必須事項の現時点でのベストプラクティスを記事に残す。

下記の環境で開発を行ってます。


環境


  • Mac OS High Sierra バージョン 10.13.6

  • Ruby 2.5.1

  • Rails 5.2.1

  • MySQL 5.7.23

  • RSpec

  • CarrierWave

  • MiniMagick

  • Fog

  • S3


CarrierWave

gem 'carrierwave'

=> まずはGemを追加

bundle exec rails g uploader Picture

=> uploaderの設定ファイルを生成

=> この設定ファイルについては後述


▼ 単一画像

bundle exec rails g migration add_picture_to_users picture:string

=> 画像をDBで扱うためにカラム追加


models/user.rb

class User < ApplicationRecord

mount_uploader :picture, PictureUploader
end

=> ActiveRecordで扱うための処理を記述


views/users/new.html.erb

<%= form_with(model: User.new, multipart: true, local: true) do |form| %>

<%= form.file_field :picture %>
<% end %>


controllers/users_controller.rb

def show 

@user = User.find(params[:id])
end

def create
user = User.new(user_params)
end

private
def user_params
params.require(:user).permit(..., picture)
end



views/users/show.html.erb

<%= image_tag(@user.picture.url, size: "30x30") unless @user.picture.file.nil? %>



▼ 複数画像

bundle exec rails g migration add_pictures_to_users pictures:json

=> 画像をDBで扱うためにカラム追加


models/user.rb

class User < ApplicationRecord

mount_uploaders :pictures, PictureUploader
end

=> mount_uploaderではなく、mount_uploadersなので注意


views/users/new.html.erb

<%= form_with(model: User.new, multipart: true, local: true) do |form| %>

<%= form.file_field :pictures, multiple: true %>
<% end %>


controllers/users_controller.rb

def show

@user = User.find(params[:id])
end

def create
user = User.new(user_params)
end

private
def user_params
params.require(:user).permit(..., {pictures: []})
end



views/users/show.html.erb

<% @user.pictures.each do |picture| %>

<%= image_tag(picture.url, size: "30x30") unless picture.file.nil? %>
<% end %>


▼ Uploaderの設定


picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base

# 環境によって保存される場所を変更する
if Rails.env.production?
storage :fog # Fogについては後述
else
storage :file
end

# storage :file の時の画像の保存場所を指定
def store_dir
if Rails.env.test? #テスト画像は一括削除できるようにフォルダを別にする
"uploads_#{Rails.env}/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
else
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end

# セキュリティのためにファイル形式を制限
def extension_whitelist
%w(jpg jpeg gif png)
end
end



▼ URLからCarrierWaveに保存

Twitterログインした後に、Twitterのアイコン画像をCarrierWave経由で保存するケースです。

tw_image_url にアイコン画像がURLで格納されてるとします。

Userモデルには既に

mount_uploader :picture, PictureUploader

が設定されているため、

remote_picture_urlという仮想属性が定義されます。


models/user.rb

mount_uploader :picture, PictureUploader

user = User.new
user.remote_picture_url = tw_image_url
user.save

=> これでTwitterから取得した画像をCarrierWave経由で保存可能です。


MiniMagick

画像のリサイズや、別のバージョンのファイル(サムネイルなど)を用意するためのGemとしてCarrierWaveと相性が良いです。

RMagickもありますが、MiniMagickが推奨されてます。

また、MiniMagickはImagemagick上で機能するRuby Gemのため、Imagemagickがインストールされてる必要があります。

Macに直接インストールするなら、

brew install imagemagick

ですし、

Dockerなどにインストールするなら、

それぞれインストールしてから使うようにしてください。

gem 'mini_magick'

=> gemをインストールします。

あとは、CarrierWaveで生成したUploaderを変えていきます。


picture_uploader.rb

class PictureUploader < CarrierWave::Uploader::Base

# MiniMagickを有効にします
include CarrierWave::MiniMagick

# このアップローダーを利用した画像の最大数を指定します。
process resize_to_fit: [800, 800]

# 上記とは別にサムネイルを別サイズで用意します。
version :thumb do
process resize_to_fill: [200, 200]
end
end


=> resize_to_fit: アスペクト比そのまま

=> resize_to_fill: アスペクト比無視

=> resize_to_limit: 最大を決定

バージョン違いで保存したファイルは下記のように参照可能。

image_tag(user.picture.thumb.url)


Fog

gem 'fog'

=> Fogを使えるように追加。Productionにだけ追加するで問題なし。


initializers/carrier_wave.rb

if Rails.env.production?

CarrierWave.configure do |config|
config.fog_credentials = {
:provider => 'AWS',
:region => Credentials.env.dig(:S3_REGION),
:aws_access_key_id => Credentials.env.dig(:S3_ACCESS_KEY),
:aws_secret_access_key => Credentials.env.dig(:S3_SECRET_KEY)
}
config.fog_directory = Credentials.env.dig(:S3_BUCKET)
end
end

S3側の設定などは検索をしてください。


RSpec

テストでは下記の2つを確認しようかと思います。


  1. 適切なファイルフォーマットなら保存可能か、不適切なら保存不可か

  2. リサイズが適切にされているか。サムネイルも指定サイズで作成されているか


▼ 下準備

ファイルの大きさなどを検証するためのmatchersを追加するために下準備します。


spec/rails_helper.rb

require 'carrierwave/test/matchers'



spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
include CarrierWave::Test::Matchers
end



spec/fixtures

fixtures配下にいろんなファイル形式、サイズのものを保存しておいてください。

- sample.jpg <= かなり大きいサイズの画像にしておく(リサイズ検証のため)
- sample.jpeg
- sample.gif
- sample.png
- sample.rb


▼ ファイルフォーマット


spec/models/user_spec.rb

it "is valid with jpg, jpeg, gif, png" do

formats = %w(jpg jpeg gif png)
formats.each do |format|
image_path = File.join(Rails.root, "spec/fixtures/sample.#{format}")
user = FactoryBot.build(:user, picture: File.open(image_path))
expect(user).to be_valid
end
end

it "is invalid with rb" do
image_path = File.join(Rails.root, "spec/fixtures/sample.rb")
user = FactoryBot.build(:user, picture: File.open(image_path))
expect(user).not_to be_valid
end



▼ リサイズ


spec/models/user_spec.rb

it "resize to small size" do

image_path = File.join(Rails.root, "spec/fixtures/sample.jpg")
user = FactoryBot.create(:user, picture: File.open(image_path))
expect(user.picture).to be_no_larger_than(800, 800)
expect(user.picture.thumb).to have_dimensions(30, 30)
end