Railsで、超簡単便利なPaperClipを使って、複数画像をS3にアップロード


Railsでファイルアップロードといえば、PaperClipかCarrierWaveっていうことらしいんだけど、今回はシンプルなPaperClipにしてみる。そんでもって、1個の画像じゃなくて、複数の画像をAWSのS3にアップロードする。サムネイルも簡単に作れるし、便利ですね。

# 前提

- Rails v4.1
- Gem : PaperClip v4.2.0
- Gem : AWS-SDK v1.54.0
- Gem : RMagick v2.13.3

## 必要なパッケージ

RMagickを使うため、事前にImageMagickのインストールが必要。

```bash:command
$ sudo yum install ImageMagick ImageMagick-devel
```

# Railsプロジェクトの生成

```bash:command
$ rails new pictures
$ cd pictures
```

さくっと、Railsプロジェクトを生成。

## Gemfile

```ruby:Gemfile
#(snip)

#Image
gem 'rmagick'
gem 'paperclip'

#AWS
gem 'aws-sdk'
```

```bash:command
$ bundle install --path vendor/bundler
```

これで、もろもろ必要なGemたちがインストールされる。

# モデルの生成

```bash:command
$ bundle exec rake db:create
$ bundle exec rails g scaffold article title:string description:text
$ bundle exec rails g model picture article_id:integer
$ bundle exec rake db:migrate
```

![スクリーンショット 2014-10-12 14.44.20.png](https://qiita-image-store.s3.amazonaws.com/0/2152/1a8821a0-ee89-806b-0bdd-79364b9a935b.png "スクリーンショット 2014-10-12 14.44.20.png")

こんな感じのリレーションモデル。Pictureモデルの`image_xxxx`な4つのカラムは、PaperClipが自動で作ってくれるので、自分では`article_id`だけ用意してあげればいい。

## 画像ファイル用のカラムを作成

```bash:command
$ bundle exec rails g migration AddAttachmentImageToPicture
```

```ruby:db/migrate/0000_add_attachment_image_to_picture.rb
class AddAttachmentImageToPicture < ActiveRecord::Migration
  def self.up
    change_table :pictures do |t|
      t.attachment :image
    end
  end

  def self.down
    drop_attached_file :pictures, :image
  end
end
```

```bash:command
$ bundle exec rake db:migrate
```

これでマイグレーションして適用させると、Pictureモデルに4つのカラムが追加されている。これで添付画像の情報を管理する。

```sql:pictures
CREATE TABLE "pictures" (
	"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 
	"article_id" integer, 
	"created_at" datetime, 
	"updated_at" datetime, 

	-- 以下4つのカラムが自動追加されている
	"image_file_name" varchar(255), 
	"image_content_type" varchar(255), 
	"image_file_size" integer, 
	"image_updated_at" datetime
);
```
## Articleモデルにアソシエーション情報を追加

Articleに複数の画像が紐づくので、has_manyでPicutreモデルを指定。そんで、Articleを削除する際に、一緒に画像ファイルも消したいので、`:dependent`で`:destroy`を指定しておく。これでわざわざ削除するロジックを気にすることはなくなる。

```ruby:app/models/article.rb
class Article < ActiveRecord::Base
  has_many :pictures, :dependent => :destroy
end
```

## Pictureモデルに画像の情報を追加

```ruby:app/models/picture.rb
class Picture < ActiveRecord::Base
  belongs_to :article

  has_attached_file :image,
                    :storage => :s3,
                    :s3_permissions => :public,
                    :s3_credentials => "#{Rails.root}/config/s3.yml",
                    :path => ":attachment/:id/:style.:extension"

  do_not_validate_attachment_file_type :image
end
```

# AWS S3の設定

```ruby:config/s3.yml
bucket: 'bucket-name'
access_key_id: 'ACCESS_KEY'
secret_access_key: 'SECRET_ACCESS_KEY'
s3_host_name: 's3-ap-northeast-1.amazonaws.com'
```

s3_host_nameは、リージョンによって違う。

| リージョン名 | ホスト名 |
|:-----------|:-----------|
|Asia Pacific (Tokyo) |s3-ap-northeast-1.amazonaws.com|
|Asia Pacific (Singapore) |s3-ap-southeast-1.amazonaws.com|
|US Standard|s3.amazonaws.com|
|US-West (Northern California) |s3-us-west-1.amazonaws.com|
|EU (Ireland) |s3-eu-west-1.amazonaws.com|

# Viewの設定

## 投稿フォーム

scaffoldですでに投稿フォームができているので、ファイル添付のフォームパーツを追記してあげる。

```erb:app/views/articles/_form.html.erb
<%= form_for @question, :html => { multipart: true } do |f| %>
```

まず、画像ファイルをアップロードするので、`<form>`タグのenctypeをmultipart指定してあげる。

```erb:app/views/articles/_form.html.erb
<ul>
	<li>ファイル１：<%= file_field_tag "images[]", type: :file %></li>
	<li>ファイル２：<%= file_field_tag "images[]", type: :file %></li>
</ul>

<!-- ↓↑どちらか -->

<ul>
	<li>ファイル複数選択：<%= file_field_tag "images[]", type: :file, multiple: true %></li>
</ul>
```

続いて、適当な場所に`<input type="file">`なフォームパーツを設置。mutipleを指定すると、一度に複数のファイルを選択することが可能。

## 表示ページ

```erb:app/views/articles/show.html.erb
<% unless @article.pictures.empty? %>
    <p>画像です</p>
    <ul>
      <% @article.pictures.each do |pic| %>
          <li id="picture_<%= pic.id %>">
            <%= image_tag pic.image.url %>
          </li>
      <% end %>
    </ul>
<% end %>
```

# Controllerの設定

ここで`params[:images]`にアップロードファイルの情報が入ってくるので、1つずつhas_manyなpicturesに保存していく。実際には、保存成功失敗の成功可否を取得してあげたほうが良いと思う。

## 新規登録

```ruby:app/controllers/articles_controller.rb
  def create
    @article = Article.new(article_params)

    respond_to do |format|
      if @article.save

        # 画像のアップロード対応
        if params[:images]
          params[:images].each { |image|
            @article.pictures.create(image: image)
          }
        end

        format.html { redirect_to @article, notice: 'Article was successfully created.' }
        format.json { render :show, status: :created, location: @article }
      else
        format.html { render :new }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end
```

また、受け取るパラメータとして、`:pictures`を追記してあげる。

```ruby:app/controllers/articles_controller.rb
    def article_params
      params.require(:article).permit(:title, :description, :pictures)
    end
```

## 更新

すでに登録済みの画像は全て削除して、新しくアップロードする画像に差し替えるようにする。

```ruby:app/controllers/articles_controller.rb
  def update
    respond_to do |format|
      if @article.update(article_params)

        # 画像のアップロード対応
        if params[:images]

          # 前回登録してある画像は全て削除
          Article.find(params[:id]).pictures.each do |image|
            image.destroy
          end

          # 代わりに今回アップする画像に差し替え
          params[:images].each { |image|
            @article.pictures.create(image: image)
          }
        end

        format.html { redirect_to @article, notice: 'Article was successfully updated.' }
        format.json { render :show, status: :ok, location: @article }
      else
        format.html { render :edit }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end
```

## ファイルアップロードの完成

ここまでの設定とコードで、とりあえず複数のファイルを新規アップロードすることが可能なはず。

# サムネイルサイズの指定

PaperclipはImageMagickを使って、様々なサムネイルサイズの画像を作って保存することが出来る。

```ruby:app/models/picture.rb
  has_attached_file :image,
                    :storage => :s3,
                    :s3_permissions => :public,
                    :s3_credentials => "#{Rails.root}/config/s3.yml",
                    :path => ":attachment/:id/:style.:extension",
                    :styles => {
                      :original => '1980x1680>',
                      :square => '100x100#',
                      :medium => '300x240>'
                    }
```

`:styles`で、複数の画像サイズを指定可能。

| 指定方法 | 効果 |
|:-----------|:-----------|
| 100x100> | アスペクト比を維持して、長辺を100pxに合わせ縮小する|
| 100x100^ | アスペクト比を維持して、短辺を100pxに合わせ縮小する|
| 100x100# | 短辺を100pxに合わせ縮小し、中央でトリミングする|
| 100x100! | アスペクト比を無視して、縦横100pxに合わせ縮小する|

よく使うのは`>`と`#`の2つかな。

## Viewファイルでのサムネイル画像参照

サムネイルの種類を名前で指定してあげればOK。

```erb:app/views/articles/show.html.erb
:medium<br>
<%= image_tag pic.image.url(:medium) %><br>

:square<br>
<%= image_tag pic.image.url(:square) %><br>
```