Active StorageによるRails ファイルアップロード

<a href="https://qiita.com/ebi_ebi/items/db485cb5d8dd2b7f2ce2">CarrierWave</a>とActive Storageでのファイルアップロードの説明を書きます。
この記事はActive Storageになります。

## 前提
AWSアカウント作成済み
IAMユーザのアクセスキーを取得済み
S3のバケット作成済み

## Active Storageとは
Rails5.2から使えるようになったRails標準の機能になります。
CarrierWaveをよく使っていましたが、今後はActive Storageを使う機会が
増えると思います。
ですが、バリデーションの機能はないため、自前で作成する必要があります。

## 環境
ruby  2.5.1
rails 5.2.2

## githubでソースを公開しています。
https://github.com/ebi5714/file_uploader

## 手順
### gemを追加

```ruby:Gemfile
gem 'mini_magick', '~> 4.8'
gem "aws-sdk-s3", require: false
gem "figaro", "~> 1.1.1"
```

mini_magick: 画像をリサイズする為に必要なgem
aws-sdk-s3: s3にファイルアップロードする為に必要なgem
figraro:     環境変数を設定するgem

```shell
$ bundle install --path vendor/bundler
```

### Active Storageの設定
下記のコマンドを実行して
active_storage_blobs
active_storage_attachments
のテーブルを作成します。

```shell
$ bundle exec rails active_storage:install
$ bundle exec rake db:migrate
```

### figaroの設定
下記のコマンドを実行して、application.ymlを生成します

```shell
$ bundle exec figaro install
```

### s3の設定
ここでaccess_keyやsecret_access_key,backet名は
application.ymlに設定しています(figraroのgem)
application.ymlにはaccess_keyなどが記載されていますので
.gitignoreに追記をします。

```ruby:.gitignore
# Ignore application configuration
/config/application.yml
```

```ruby:config/application.yml
AWS_ACCESS_KEY_ID: "○○○○"
AWS_SECRET_ACCESS_KEY: "○○○○"
AWS_BUCKET: "○○○○"
DATABASE_USERNAME: "root"
DATABASE_PASSWORD: ""
```

`storage.yml`のamazonの箇所のコメントを外します。

```ruby:config/storage.yml
# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
amazon:
  service: S3
  access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
  secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>
  region: ap-northeast-1
  bucket: <%= ENV["AWS_BUCKET"] %>
```

`development.rb`のstorageのserviceをamazonにします（上記のキー名）
今回はローカル環境のdevelopmentのenvironmentで設定をしますが
皆さんの開発環境でローカルはfile storage、本番環境はaws s3の場合は
適時変更してください。

```ruby:config/environments/development.rb
  # Store uploaded files on the local file system (see config/storage.yml for options)
  config.active_storage.service = :amazon
```

### モデルの作成、修正

Commentモデルを作成します。
contentカラムを追加します。

```shell
$ bundle exec rails g model comment
```

```ruby:db/migrate/20190121053311_create_comments.rb
class CreateComments < ActiveRecord::Migration[5.2]
  def change
    create_table :comments do |t|
      t.string   :content

      t.timestamps
    end
  end
end
```

```shell
$ bundle exec rake db:migrate
```

Commentモデルに
１つの画像を送付する場合は```has_one_attached```を指定します。
複数の画像を送付する場合は```has_many_attached```を指定します。


```ruby:app/models/comment.rb
class Comment < ApplicationRecord
  has_one_attached :icon
  has_many_attached :images
end
```

### コントローラーの作成

```shell
$ bundle exec rails g scaffold_controller comments 
```

コントローラーは`permit`に`icon`と`images`を追加します。
imagesは配列で送られてくる為、`images:[]`と指定します。

```ruby:app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  before_action :set_comment, only: [:show, :edit, :update]

  # GET /comments
  def index
    @comments = Comment.all
  end

  # GET /comments/1
  def show
  end

  # GET /comments/new
  def new
    @comment = Comment.new
  end

  # GET /comments/1/edit
  def edit
  end

  # POST /comments
  def create
    @comment = Comment.new(comment_params)

    respond_to do |format|
      if @comment.save
        format.html { redirect_to @comment, notice: 'Comment was successfully created.' }
      else
        format.html { render :new }
      end
    end
  end

  # PATCH/PUT /comments/1
  def update
    respond_to do |format|
      if @comment.update(comment_params)
        format.html { redirect_to @comment, notice: 'Comment was successfully updated.' }
      else
        format.html { render :edit }
      end
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_comment
      @comment = Comment.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def comment_params
      params.fetch(:comment, {}).permit(:content, :icon, images: [])
    end
end
```

### アップロード
viewでは複数ファイルの送付の場合は`multiple: true`を指定します。

```html:app/views/comments/_form.html.erb
<%= form_with(model: comment, local: true) do |form| %>
  <% if comment.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(comment.errors.count, "error") %> prohibited this comment from being saved:</h2>

      <ul>
      <% comment.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="actions">
    アイコンアップロード<br /><%= form.file_field :icon %><br>
    イメージアップロード<br /><%= form.file_field :images, multiple: true %><br>
    <%= form.text_area :content %><br>
    <%= form.submit %>
  </div>
<% end %>

```

### 画像表示
1つの画像(icon)を表示する場合と複数の画像(images)を表示する場合
resizeをする場合は下記のように記載します。

```html:app/views/comments/index.html.erb
<% if comment.icon.attached? %>
  <%= image_tag comment.icon.variant(resize: "200x200") %>
<% end %>

<% if comment.images.attached? %>
  <% comment.images.each do |image| %>
    <%= image_tag image.variant(resize: "200x200") %>
  <% end %>
<% end %>
```