Rails4.2+carrierwave+fog+rmagickでS3に画像アップロードした話

業務でアイコン登録機能を追加することになり、
画像・ファイルアップロード機能はこれから先どんなシステムでも
必要とされるだろうと思い、書き残しておきます。

最近Rails5系が出てきたので、そろそろ4系の情報が必要とされなくなる？かも
* * *

# 使用技術
Ruby2.1.2
Rails4.2
carrierwave # 画像アップロード用
rmagick     # トリミング用
fog         # S3へのアップロードを簡単にしてくれるライブラリ
* * *

# やったこと
## ー導入編ー
今回のファイルアップロード先はAWS S3になったので、AWSの設定をします。
AWSの設定は、
1. AWSアカウント作成
2. IAMユーザー作成
3. S3にてバケット作成
ていう流れになるかと。
詳細な手順は以下の引用元を参考にしてみてください。今回は省略します。
[AWSアカウント作成](https://aws.amazon.com/jp/register-flow/)
[IAMユーザー作成とバケット作成](https://cloudpack.media/5726)
[RailsとCarrierWaveでAmazon S3に画像を保存する](http://www.workabroad.jp/posts/1186)

この段階で、AWSのアクセスキーIDとシークレットアクセスキーを取得しましょう。
* * *

次はアプリ側の設定です。Gemfileに以下を記載します。

```Gemfile
gem 'carrierwave'
gem 'rmagick'
gem 'fog'
```
bundle install します。

※自分はbundle install でrmagickのインストール中に
checking for Magick-config... no
というエラーが出たので、以下で対応しました。

```
$ brew install imagemagick
$ gem install rmagick
$ bundle install
```
ここまで出来たら、Carrierwaveの設定をします。
* * *

まずは、envファイルに先ほど取得したAWSのキーとリージョン、バケット名とバケットまでの
URLを記載します。

```
AWS_ACCESS_KEY_ID = 先ほど取得したアクセスキー
AWS_SECRET_ACCESS_KEY = 先ほど取得したシークレットアクセスキー
AWS_REGION= ap-northeast-1 # 東京
AWS_S3_BUCKET= バケット名
AWS_S3_URL= バケットまでのURL

```
* * *
config配下にcarrierwave.rbを作成し、以下記載

```carrierwave.rb
CarrierWave.configure do |config|
  config.fog_credentials = {
    provider: 'AWS',
    aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
    aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
    region: ENV['AWS_REGION'],
    path_style: true
  }
  config.fog_public     = true # public-read

  config.remove_previously_stored_files_after_update = false

  config.fog_directory = ENV['AWS_S3_BUCKET']
  config.asset_host = ENV['AWS_S3_URL']

end
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/
```
* * *
ここで活躍してくれるのが**fog**です。fogがAWSの認証をしてくれます。
今回は環境毎にenvを書き換える形でバケットを変えています。
`case when` とかで環境毎にバケットを変えることもできます。

`CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/`
これは、日本語のファイル名が____に置き換わってしまうのを防ぐために記載しています。

ここまでで、設定はほぼ完了です。

* * *

## ーアップロード編ー
続いて、実際にAWS S3に画像をアップロードします。
アップロードクラスを作成します。
業務ではユーザーアイコンを設定する機能だったので、iconという名前にしています。

`bundle exec rails g uploader icon`

これで、app/uploaders/icon_uploader.rbが作成されます。
* * *

続いてicon_uploader.rbを使いやすいように修正します。

```icon_uploader.rb
class IconUploader < CarrierWave::Uploader::Base

  include CarrierWave::RMagick # 画像トリミングしますよー

  storage :fog # fog使いますよー
  
  # cash使います。
  def fog_attributes
    {
      'Content-Type' =>  'image/jpg',
      'Cache-Control' => "max-age=#{1.week.to_i}"
    }
  end

  # バケット以下アイコンの保存先を指定します。
  # ~/[バケット名]/[foldername]　配下に画像がアップロードされます。 
  def store_dir
    "[foldername]"
  end
  
  # アップロード可能な形式をここで指定します。
  # ちなみにアップロード不可な形式の指定はextension_black_list
  def extension_white_list
    %w(jpg jpeg png)
  end

  # アップロード時のファイル名を指定します。
  # アップロードしたファイルを一意に認識
  def filename
    if original_filename.present?
      "#{model.id}_#{secure_token}.#{file.extension}"
    end
  end

  protected
  def secure_token
    var = :"@#{mounted_as}_secure_token"
    model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
  end

end

```
* * *

Modelのカラムにアップローダーを紐付けます。

```user.rb
  # ↓ 以下を追加
  mount_uploader :icon, IconUploader
```

migrationファイルに作成したアップローダーと同じ名前でカラムを追加します。
* * *

ここまで設定したら、migrate
`bundle exec rake db:migrate`
* * *

これで、viewにファイルアップロードを追加すれば画像がアップロードされます。
画面に出すときには以下のコードを追加すればOKです。

```
<%= image_tag model.icon_url %>

```
model.icon_urlで勝手にS3までのurlを付加してくれます。
* * *
アイコンをヘッダーとかで常に表示したい場合には、付加してくれる画面とそうでない画面
がcontrollerで分かれるので、URL設計はしないといけません。

これでRails+carrierwave+fogでs3に画像アップロードは出来ました。

* * *
## ートリミング・リサイズ編ー
最後に画像のトリミング・リサイズです。
トリミング・リサイズにはRmagickを使います。
Rmagickはいろんなメソッドがあるので、自分が意図する形でトリミングしてくれるように
メソッドを使います。
リサイズするだけなら、この記事がすごくわかりやすいです。
[CarrierWave + RMagick 画像のリサイズをまとめてみました](http://noodles-mtb.hatenablog.com/entry/2013/07/08/151316)

* * *
ということで、自分も要件で100x100サイズに画像をリサイズする必要があったので、
アップローダーに以下記載します。

```icon_uploader.rb
 version :thumb do
    process :resize_to_limit => [100, 100]
 end
```
* * *
これで、オリジナルとは別に100x100で画像がアップロードされるようになります。
また、`resize_to_limit`は縦横比固定でアップロードしてくれます。


でもでも、このままアップロードすると、横長の画像が縦長に表示されるので、
結局トリミングしないといけませんでした。
* * *
## 解決策
また、トリミングの際には画像に対して中央から100x100で切り取るようにしないといけません。
まぁ、そこまで便利に作られているわけではないので、自前で拡張します。
ここの情報がすごく少なかったですね。
* * *

```icon_uploader.rb
 version :thumb do
    process :crop
    process :resize_to_limit => [100, 100]
  end

 def crop
    manipulate! do |img|
      gravity = Magick::CenterGravity # 中央から切り取ります。
      crop_w = img.columns
      crop_h = img.rows
      # 画像のサイズが縦横違った場合は小さい方に合わせてトリミングする。
      if img.rows <= img.columns
        crop_w = img.rows
      else
        crop_h = img.columns
      end
      img.crop!(gravity, crop_w, crop_h)
      img = yield(img) if block_given?
      img
    end
```

* * *
`version :thumb`で、自前でトリミングをして、縦横サイズを合わせて
100x100にリサイズしています。

これで、縦横の長さが違くても綺麗に表示されるようになります。
`crop`メソッドが切り取りを行います。
そこの情報は以下を参考にしました。
[RubyのRMagickで縦横比固定でリサイズしたり切り抜いたり](http://blog.ruedap.com/2011/03/22/ruby-rmagick-imagemagick-resize-crop)

以上で、画像のトリミング・リサイズは終わりです。

* * *
# 実装してみて
画像アップロードは初めてだったので、いろいろ調べたり先輩のコードを読みながら
進められたので比較的サクサクできたかなと。
ただ、情報がちらばっていたので、画像アップロードの導入からトリミング・リサイズまで
ある詳細な記事があったら便利だなと思ったので今回書いてみました。

facebookやtwitterとかもアイコン登録がありますが、アップロード時に任意の場所で
トリミングさせていますよね。
今回は自分の技術力不足によって実装期間に余裕が持てませんでしたが、余裕があれば
取り組みたいところです。