Ruby
Rails

[Rails5] Carrierwave + S3 + CloudFrontで画像アップロードを実装する方法

More than 1 year has passed since last update.

環境

ruby: 2.3.3
imagemagick: 6.8
rails (5.1.1)
carrierwave (1.1.0)
fog-aws (1.3.0)
rmagick (2.16.0)

概要

本記事では、AWSの使い方には触れてません。Rails部分のみです。

Carrierwaveとは

画像アップロード用のライブラリです。

fogとは

クラウドサービス連携用のライブラリです。AWSだけでなく、Azule, GCPなども用意されてます。
https://github.com/fog/fog

fog is the Ruby cloud services library, top to bottom:

rmagickとは

画像処理ソフトウェアのImageMagickをRubyで使えるようにするライブラリです。
https://github.com/ImageMagick/ImageMagick

ImageMagick® is a software suite to create, edit, compose, or convert bitmap images.

別途、サーバにImageMagickがインストールされている必要があります。

例: Ubuntu14.04でのインストール

sudo apt-get update
sudo apt-get install -y imagemagick libmagick++-dev

Carrierwave入門

公式に沿って、UserモデルにAvater画像をアップロードできるようにします。
https://github.com/carrierwaveuploader/carrierwave
Getting Started
Using Amazon S3
Using RMagick

Gemfile

# 画像アップロード用
gem 'carrierwave'
# AWS S3連携用
gem 'fog-aws'
# 画像処理用(例: リサイズ)
gem 'rmagick'
bundle install

まずは、Uploaderを作成します。

rails generate uploader Avatar

以下ファイルが生成されます。

app/uploaders/avatar_uploader.rb

次に、UserモデルにAvatarカラムを追加します。
(Userモデルが事前に存在する前提になっています。※)

rails g migration add_avatar_to_users avatar:string
rails db:migrate

※存在しない場合は、適当にUserモデルを作成します。

rails g model User name:string email:string password_digest:string
rails db:migrate

追加したAvatarカラムを、mount_uploaderに定義します。

class User < ActiveRecord::Base
  mount_uploader :avatar, AvatarUploader
end

Carrierwave 設定カスタマイズ(サンプル)

app/uploaders/avatar_uploader.rb

最初から色んなサンプルが記載されており親切です。

app/uploaders/avatar_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base

  # 画像リサイズ用にRMagickをinclude
  include CarrierWave::RMagick

  # iPhoneから画像投稿した際に、画像の向きがおかしい場合があるので、
  # Rmagickのauto_orientメソッドで向きを正す。
  process :fix_rotate
  def fix_rotate
    manipulate! do |img|
      img = img.auto_orient
      img = yield(img) if block_given?
      img
    end
  end

  # Choose what kind of storage to use for this uploader:
  # ストレージの設定(S3アップロード用にfogを指定)
  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

  # Add a white list of extensions which are allowed to be uploaded.
  # 許可するファイル拡張子の設定
  def extension_whitelist
    %w(jpg jpeg gif png)
  end

  # Override the filename of the uploaded files:
  # ファイル名の設定(以下はランダムな16進数文字列をファイル名の先頭に付与している例)
  def filename
     "#{secure_token(10)}.#{file.extension}" if original_filename.present?
  end

  protected
  def secure_token(length=16)
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.hex(length/2))
  end

  # Create different versions of your uploaded files:
  # リサイズの設定(要RMagick)
  # 1つだけではなく複数のversionを設定可能
  version :thumb do
      process resize_to_fit: [50, 50]
  end
end

補足

iPhoneから画像投稿した際に、画像の向きがおかしい場合があるので、
Rmagickのauto_orientメソッドで向きを正す。

iPhoneに保存された写真は向きの情報を持っているが、
ブラウザによってはそれを無視して表示してしまうことが原因です。

参考: アップロードした写真(画像)が回転して表示されるのを直す方法
http://qiita.com/hiro_y/items/0476bcf39a77ca184009

ファイル名の設定(以下はランダムな16進数文字列をファイル名の先頭に付与している例)

READMEには記載がないですが、公式wikiに詳細が載っています。
基本はUUIDを用いたユニークな文字列が推薦されています。

参考: How to: Create random and unique filenames for all versioned files
https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Create-random-and-unique-filenames-for-all-versioned-files

config/initializers/carrierwave.rb

デフォルトだと存在しないので作成します。
S3, CloudFrontの設定情報を記載します。
私の場合は、概ねの設定をFigaroで環境変数に設定しています。

config/initializers/carrierwave.rb
CarrierWave.configure do |config|
  config.fog_provider = 'fog/aws'
  config.fog_credentials = {
    provider: 'AWS',
    aws_access_key_id: ENV['fog_api_key'],        # AWSアクセスキー
    aws_secret_access_key: ENV['fog_api_secret'], # AWSシークレットキー
    region: ENV['fog_region'],                    # S3リージョン
    host: ENV['fog_host']                         # S3エンドポイント名(例: s3-us-west-2.amazonaws.com)
  }

  # キャッシュもS3に置くようにする
  config.cache_storage = :fog
  config.cache_dir = 'tmp/image-cache'

  # S3バケット名
  config.fog_directory = ENV['fog_directory']

  # CloudFrontのDomainName or CNAME(例: http://xxx.cloudfront.net)
  # CDNを使わない場合は、S3バケットの絶対パス(例: https://s3-us-west-2.amazonaws.com/<backet>)
  config.asset_host = ENV['fog_asset_host']
end

app/models/user.rb

このままだと画像を削除してもS3には残ってしまうため、
before_destroyでS3の画像を削除します。

app/models/user.rb
class User < ActiveRecord::Base
  mount_uploader :avatar, AvatarUploader
  before_destroy :clean_s3
private
  def clean_s3
    avatar.remove!       #オリジナルの画像を削除    
    avatar.thumb.remove! #thumb画像を削除
  rescue Excon::Errors::Error => error
    puts "Something gone wrong"
    false
  end
end

参考: https://stackoverflow.com/questions/22391183/carrierwave-destroy-object-only-after-mounted-files-have-been-deleted-from-sto

view側の設定

フォームサンプル

<%= form_for(@user) do |f| %>
  <%= f.file_field :avatar %>
  <%= f.submit 'Upload' %>
<% end %>

file_fieldにAvatarカラムを設定します。

画像の表示(取得)方法

<%= image_tag user.avatar.thumb.url %>

thumbの部分はversion名と合わせる必要があります。

app/uploaders/avatar_uploader.rb
  version :thumb do
      process resize_to_fit: [50, 50]
  end

オリジナル画像を取得する場合は、user.avatar.urlです。