PaperclipとAWS S3を用いた画像アップロード機能作成手順まとめ

## はじめに

昨年からRuby on Railsでもお仕事をする機会があり、その中で自分が得たものや自分で作成したアプリの中で使用している技術やTipsに関しても少しずつQiitaに公開していければと思います。今回は自分が公開している簡易家計簿アプリ「Coffre」でも使用している画像のアップロードに関しての実装方法についてまとめてみたいと思います。

![promotion_coffre.jpg](https://qiita-image-store.s3.amazonaws.com/0/17400/b4705fc1-f3be-574e-e07c-e94cc8065c53.jpeg)

+ [Coffre公式ページ](http://www.coffre.me/)
+ [Coffreのダウンロードはこちら](https://itunes.apple.com/jp/app/coffre/id972428097?mt=8&uo=6&at=&ct=)
+ [Coffreのアプリ概要や設計・取り組んでみた感想等について](http://blog.just1factory.net/services/260)

具体的に使用している部分に関しては、管理画面側の「お知らせ・Coffreのコラム」部分についてheroku + PaperClip + AWS S3での追加・変更画面にて画像アップロードを使用している部分になります。

__（参考）Coffre管理画面：__
![coffre_admin_tool.png](https://qiita-image-store.s3.amazonaws.com/0/17400/9b0e947d-268a-4970-d967-0fba3000869b.png)

（Rails歴は個人的に触ってみるのも合わせて1年程度の経験しかありませんが、徐々にサーバーサイドの言語に関する設定についてもTipsを公開できればと思っています）

私のRubyとRailsの環境：

+ OS：Yosemite 10.10.5 ※まだEl Capitanにはしていません><
+ パッケージ管理：Macports 2.3.3
+ Ruby：ruby 2.1.6p336
+ Rails：4.1.7
+ サーバー：heroku

バージョンがかなり出遅れていますのであくまで参考程度に読んでいただければ幸いです。

## 1. 参考にしたドキュメントや資料まとめ

※herokuのアカウント及びAWSのアカウントの作成＆S3の利用ができるまでの設定ができている前提でのお話になりますので、実際にサーバーでアプリケーションを運用する際は最初に設定をしていただければと思います。

実装に当たって、参考にしたサンプルやドキュメント一式は下記になります。特にHerokuの公式ドキュメントは英語ではありますが頑張って読んでみると実際の運用に当たってのヒント等もあったりして本当にためになります。

#### ★PaperClip

+ [公式リポジトリ](https://github.com/thoughtbot/paperclip)

こちらのリポジトリのREADMEにも詳細な解説や実装例等が掲載されています。全て英語の解説にはなっていますが、コードがあるので英語に自信がないとしても意外と追っかけることはできるかと思います（私も英語はそれほど自信がないけど、Rubyの仕事をするようになって「あ、そういうことか！」というのが少しずつわかってきました）

今は一応Githubからの通知を受け取って見ているだけですがソース自体も今後は追っていければなんて思っています。

#### ★Herokuの公式ドキュメント

下記はherokuの公式ページのドキュメント内からのハウツーになります。AWS S3での設定方法＆Paperclipの導入時のポイントなどが掲載されています。こちらのドキュメントも英語になりますが、コードや設定ファイルのサンプルが掲載されているので実際のHerokuでWebサービスやアプリのAPIを運用する際にも活用できそうな気がします。

+ [Using AWS S3 to Store Static Assets and File Uploads](https://devcenter.heroku.com/articles/s3)
+ [Direct to S3 Image Uploads in Rails](https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails)
+ [Uploading Files to S3 in Ruby with Paperclip](https://devcenter.heroku.com/articles/paperclip-s3)

私も導入の際はこちらのドキュメントを参考にしましたし、まだまだ深掘り足りない部分もありますので実際にHerokuで運用するようなサービスを作成する場合にはしっかりと読んでおこうと思います。

#### ★AWS S3＋Paperclipのストレージ保存の部分の実装参考

AWS S3への画像アップロードする部分の設定及びPaperclipでの実装に関しての参考資料として活用をしました。今回の手順は上記でも紹介したPaperclipに関する英語ドキュメント類と下記の導入に関する記事を参考に作成してまとめています。

+ [[Ruby on Rails]paperclipでつくるS3への画像保存アプリ](http://dev.classmethod.jp/server-side/ruby-on-rails/ruby-on-railspaperclip_s3_image_store/)

+ [RailsでPaperclipを使ってファイルをアップロードする](http://ruby-rails.hatenadiary.com/entry/20140716/1405443484)

ほかにもQiitaの記事やブログ等でもPaperclipに関しての実装メモやブログ等を書かれている方もいらっしゃいますので、そちらも色々調べて見ると情報が得られると思いますので気になった情報を見つけられた方はぜひ教えていただけると嬉しいです(^_^)

## 2. AWSまわりの下準備とGemのインストール

今回のサンプルでは、herokuやAWSのアカウント登録が済んでからアプリケーションを作成するということを想像した「かいつまんだ」手順になりますので、すでにRailsの準備が整っている方やパッケージ管理にrbenvを使用している方の手順とは少しだけ異なるかもしれませんが、ご参考になれば幸いです。

#### ★ImageMagickを自分のMacにインストールする

Paperclipでまずは画像操作をするためのGemなのでまずは自分のMacにImageMagickをインストールする必要があります。私の場合はパッケージ管理ツールにMacportsを利用していたので、下記の情報を参考にしてインストールしました。

```
$ sudo port install ImageMagick
(brewコマンドでインストールする場合)
$ brew install ImageMagick
```

+ （参考）[MacにImageMagickをインストールする方法](http://hisasann.com/housetect/2010/02/macimagemagick.html)

これで自分のMac内で動かしているサンプルアプリケーションから `localhost:3000 or 0.0.0.0:3000` から画像をアップロードする準備ができました。

#### ★AWS S3のバケットを作成して諸々の設定を行う

次にAWS S3側の設定を行います。こちらの設定方法に関しては上記のherokuにもその方法の記載がありますが、まずはバケットを画面から作成します。その後に作成したバケットに関しての諸々の設定を行っていく形になります。（バケットのリージョンは「Tokyo」を指定）

バケット作成後に具体的に設定するものとしては、

+ アクセス権
+ バケットポリシー
+ CORS

の3つになります。下記にAWS S3の画面のキャプチャと解説も掲載しておきましたので参考になれば幸いです。

__（参考）AWS S3の実際の画面：__
![aws_s3_panel.png](https://qiita-image-store.s3.amazonaws.com/0/17400/e9801e58-5dd5-887d-c58a-0d03f5008999.png)

またバケットポリシーやCORSの設定に関しては下記のように設定をしてあげればOKです。この辺りに関してはherokuの公式ドキュメントを参考にして設定しました。

__（参考）バケットポリシーの設定：__

```json
{
    "Version": "2012-10-17",
        "Statement": [
            {
                "Sid": "PublicReadGetObject",
                "Effect": "Allow",
                "Principal": "*",
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::(作成したバケット名)/*"
            }
        ]
}
```

__（参考）CORSの設定：__

```xml
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>
```

※ AWSに関してはかなり正直自信なしですが、実際に今の所一応いたずらされたり等はなんですけど間違い等あればどなたかご指摘くださいませ＞＜

#### ★libxml2を自分のMacにインストールする

今回使用するGemの中で`aws-sdk`というGemを使うのですがこちらが、NokogiriというGemと関連があるのでこちらも忘れずにインストールしてあげます。

+ （参考）[Nokogiriの基本(翻訳版)](http://www.engineyard.co.jp/blog/2012/getting-started-with-nokogiri/)

上記はスクレイピングを行う際によく使われるGemではあるのですが、こちらのインストールに関してはRailsの環境を作る際に結構ハマりやすい部分の一つでもあります。私の環境では下記のようなコマンドでインストールをしました。

```
$ sudo port install libxslt +universal
$ sudo port install libxml2 libxslt 
$ sudo gem install nokogiri -- --use-system-libraries=true --with-xml2-include=/usr/include/libxml2/ 
```

最初のAWS S3の設定をまったく知らなかった事と合わせて、自分がパッケージ管理をmacportsを使っていたので、なかなか情報が少なかったので最初はなかなか苦労しました。。。AWS S3の設定周りの知識やRubyの環境構築に関する知識、そして英語頑張らなきゃと反省と自分への自戒を込めて感じました。

#### ★使用するGemをGemfileに記載してインストールする

上記の環境設定ができましたら、後はpaperclipとaws-sdkの2つに関するGemfileに記載して`bundle install`をしてあげれば設定完了です。

```ruby:Gemfile
gem 'paperclip', '~> 4.3'
gem 'aws-sdk', '< 2.0'
```

ファイルアップロードの機能に関しては、同様にCarrierWaveというGemもありますが、私が知ったきっかけとしてはLaravelで使っていたライブラリがPaperclipを元に作成されたものであったことがきっかけでした。そしてその時に仕事でRuby on Railsを使用していたこともあって実際に試してみたいと感じたことがきっかけです。

+ （参考）[画像アップロードのお話と僕が作っているもの [for Laravel Advent Calendar 2014]](http://blog.just1factory.net/programming/201)

上記は昨年のLaravel Advent Calendarで書いた記事になりますが、書き方が驚くほどにている部分があったので参考までにピックアップしてみました。

## 3. Scaffoldを元にしてModel側の実装＆View側の実装

今回はScaffoldで作成したアプリケーションで、実際に記述を行っていく際の書き方をまとめました。こちらのGemは公式のドキュメントがしっかり書かれているので、そちらも是非一読して頂けると幸いに思います。

#### ★AWS S3の設定をRailsに記述

この辺りの記述や設定方法に関しては下記のように先ほど作成したバケットの設定等を入れていきます。ただアクセスキーやシークレットキーの管理に関してはしっかりと管理を！

```yml:app/config/s3.yml
bucket: 自分のつくったバケット名
access_key_id: アクセスキーID
secret_access_key: シークレットキー
s3_host_name: ホスト名
```

下記の設定ファイルに関しては、S3のホスト名やURL・どのようなディレクトリ構成でファイルを配置するかに関しての設定をします。

```ruby:app/config/initializers/paperclip.rb
Paperclip::Attachment.default_options[:url] = ':自分のつくったバケットのURL'
Paperclip::Attachment.default_options[:path] = '/:class/:attachment/:id_partition/:style/:filename'
Paperclip::Attachment.default_options[:s3_host_name] = 'ホスト名'
```

設定ファイルに関しては以上になります。

#### ★Scaffoldの作成とDBマイグレーション

今回はとあるアプリケーションにてNewinfo（最新情報）の部分に画像を一つ添付できるようにしたいと思います。

その他の想定としては、

+ 画像ファイル（JPG / GIF / PNG）形式のみ
+ 画像サイズは2M以内

とした場合という想定で進めていきます。

__① Newinfoに関するScaffoldの作成：__

```
$ rails g scaffold Newinfo title:string detail:text published:date flag:integer
$ rake db:migrate
```

__② Paperclip用のマイグレーションファイルの作成：__

```
$ rails g migration AddAttachmentImageToNewinfos
```

そして上記で作成したScaffoldに対してPaperclip用のマイグレーションを作成していきます。下記のコマンドで空っぽのマイグレーションファイルが作成されるので下記のように記述をします。

__③ マイグレーションファイルに記載：__

下記のように設定してあげてPaperclipの画像情報を保持するカラムをnewinfosテーブルに追加してあげます。

```ruby:db/migrate/(timestamp)_add_attachment_image_newinfo.rb
class AddAttachmentImageToNewinfos < ActiveRecord::Migration
  def self.up
    change_table :newinfos do |t|
      t.has_attached_file :image
    end
  end
 
  def self.down
    drop_attached_file :newinfos, :image
  end
end
```

またこのマイグレーションファイルを実行されると自動的にカラムが作成されますが、具体的には下記のような構成になります。

| カラム名 | データ型 | 説明 |
|:-----------|:------------|:------------|
|image_file_name |varchar(255) |ファイル名 |
|image_content_type |varchar(255) |ファイルの種類 |
|image_file_size |integer |ファイルサイズ |
|image_updated_at |datetime |アップロード日 |

__④ Paperclip用のカラムの反映：__

```
$ rake db:migrate
```

再びマイグレーションを行えばDBへ必要なカラムを追加する作業が終わります。

#### ★Model側の実装

Model側ではPaperclipに関する指定や画像のバリデーションを行っていきます。
この中の `has_attached_file` 内で指定しているのは、

+ 画像サイズ
+ 使用ストレージ
+ 読み込む設定ファイル関連
+ 保存先のディレクトリ

となります。今回のはごくごく基本的な設定にはなりますがもっと深掘りしたいですね。

```ruby:app/models/newinfo.rb
class Newinfo < ActiveRecord::Base
  
  #添付ファイルアップロードに関する設定
  has_attached_file :image,
    :styles => {
      :thumb  => "100x100#",
      :medium => "200x200#",
      :large  => "640x320#"
    },
    :storage => :s3,
    :s3_permissions => :private,
    :s3_credentials => "#{Rails.root}/config/s3.yml",
    :path => ":class/:attachment/:id/:style.:extension"
    
  #画像ファイルに関するバリデーション
  validates_attachment_content_type :image, 
    :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]

  validates_attachment_size :image,
    :less_than => 2.megabytes

end
```

画像のサイズ設定バリエーションに関しては下記のリンクを参考にしていただけると良いかと思います。

+ （参考）[Rails4+Paperclip(ImageMagick)のサムネイルサイズ一覧](http://blog.noriaki.me/2014-01-02-rails4-paperclip-imagemagick-thumbnail-styles)

#### ★View & Controller側の実装

そして最後にControllerとViewに画像のアップロード部分の処理を書けばOKです。

__① Controllerの処理：__

```ruby:app/controllers/newinfos_controller.rb
class NewinfosController < ApplicationController

    --- (省略) ---

    # Never trust parameters from the scary internet, only allow the white list through.
    def newinfo_params
      #Scaffoldの場合には :image をpermit内に追記
      params.require(:newinfo).permit(:title, :detail, :published, :flag, :image)
    end
end
```

__② Viewの処理：__

```erb:app/views/newinfos/_form.html.rb
--- (一部抜粋) ---

<!-- 画像アップロード用のf.file_fieldを追記する -->
<div class="field">
  <%= f.label :image %><br>
  <%= f.file_field :image %>
</div>
```

実装自体はそんなに難しくなく、Githubを見ると画像の切り出し方のタイプやアスペクト比の調整等の例も掲載されていますので今後とも開発でお世話になっていくGemかなとも感じました。

## 4. おわりに

私の環境でのインストールの仕方がrbenvを使わない方法＆バージョンが割と古いということもあってもしかするとあまり参考になる記事かは今回は本当に自信がありませんでした。Paperclipの導入自体は手順さえ掴んでしまえば、こなれたものにはなってくるとは思いますが下準備の段階が結構大変でした。（その時に初めてAWSを使ったこともあるので...）またこの方式ですとAWSの情報をs3.yml等に書いている方法で「取り急ぎ試してみる」要素も強いため、実際の開発等ではGithubで非公開リポジトリにする等の対応等も必要かと思います。（herokuの定数として設定してあげるのがもしかすると良いのかも）

あとは折角このような記事を書いているので、実際に何かを作成して例としてもっと示す事ができればと考えている所存です。

## 追記とその他

今回は実際に自分のアカウントで導入した＆実装した手順になりますのでサンプルのGithub公開はしていませんが、気付いた点やバージョンによってこのような際があったという報告等も受け付けていますので、コメント欄等に記載していただければ幸いです。

2016.04.16

+ Paperclip 4.2.1以前では下記のような脆弱性があるので、実際に使用する際は4.2.2以上を使用する様にお願いします（Gemfile内のバージョン指定を変更）

__Paperclipの脆弱性に関する参考資料的なものとセキュリティアップデート内容：__

+ [Fix a possible security issue with spoofing](https://github.com/thoughtbot/paperclip/commit/9aee4112f36058cd28d5fe4a006d6981bd1eda57)
+ [Ruby on Rails 用ライブラリ Paperclip におけるクロスサイトスクリプティングの脆弱性](https://github.com/thoughtbot/paperclip/commit/9aee4112f36058cd28d5fe4a006d6981bd1eda57)
+ [Paperclipの脆弱性（CVE-2015-2963）
](http://qiita.com/cos65535/items/312483ef063353d36dff)