LoginSignup
45
47

More than 5 years have passed since last update.

画像周りの扱い方

Last updated at Posted at 2018-10-10

これは何?

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
45
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
47