[Rails6・S3]ActiveStorageを使って画像をダイレクトアップロードする

### 前提
記事を書いている私は現役のエンジニアではなくエンジニアを目指して学習中の者です。
よって、気をつけてはいますが記述内容には誤った内容が含まれている危険性があります。
その為この記事は参考程度に読んで頂き詳しい内容は公式の記述を参考にすることをおすすめします。

### この記事で実装すること
Rails6系でActiveStorageを使用してS3に画像をダイレクトアップロードする手順を説明します。
Railsアプリで画像を複数枚アップロードする機能を実装すると思っていたよりアップロードに時間がかかってしまいます。
そこでダイレクトアップロードを実装するとアップロードにかかる時間を大幅に削減することができます。

### 使用技術
Ruby 3.1.2
Rails 6.1.6
Heroku

### 実装手順
Active StorageはRailsにデフォルトである画像投稿のための機能なので詳しい情報はRailsガイド等も参考にしてみてください。
[Railsガイド](https://railsguides.jp/active_storage_overview.html#%E3%83%80%E3%82%A4%E3%83%AC%E3%82%AF%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89)
[実装手順を参考にした記事](https://pikawaka.com/rails/active_storage)


##### 1, アプリを作成する
```
rails new . -d postgresql --skip-test --skip-coffee --skip-turbolinks
```

##### 2, 画像投稿用のモデルを作成する
```
rails generate scaffold tweet title:string description:string
```

##### 3, active_strageのコマンドを実行
```
$ rails active_storage:install
$ rails db:migrate
```
このコマンドでactive_storage_blobs とactive_storage_attachmentsと言う2つのテーブルが作成されます。
blobsにはアップロードされたファイルが保存され、attachmentsは画像を投稿する画像とblobsとの中間テーブルになります。

##### 4, モデルに画像投稿用のカラムのようなものを紐づける
実際にカラムを追加する必要はなくモデルに以下のようにコードを追加するだけで大丈夫です。
今回は複数画像を投稿できるようにしたいので複数形のimagesにしています。
```ruby
class Tweet < ApplicationRecord
  has_many_attached :images
end
```

##### 5, ストロングパラメータにimagesを追加する
画像が投稿できるようにストロングパラメータにimagesを追加します。
[]は複数画像を投稿する場合に必要になる記述です。
```ruby
def tweet_params
  params.require(:tweet).permit(:title, :description, images: [])
end
```

##### 6, 投稿フォームと画像を表示するviewを編集
**multiple: true**をつけると複数画像が投稿できるようになります。
```erb
<div class="mb-8">
  <%= f.label :images, class: "text-sm block"%>
  <%= f.file_field :images, multiple: true %>
</div>
```
複数画像なので表示する場合は**eachメソッド**を使用しています。
```erb
<% @tweet.images.each do |image| %>
  <%= image_tag image %>
<% end %>
```
ここまででActive Storageを使用して画像が投稿できるようになったと思います。

##### 7, アップロード先をS3に変更する
AWSのS3にバケットを作成する方法についてはここでは省略させてもらいます。
S3にバケットを作成してから以下の設定を行うようにしてください。

- gemをインストールする。
```
gem 'aws-sdk-s3'
```

- configディレクトリのstrage.ymlファイルを編集します。
```yml
test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
amazon:
  service: S3
  access_key_id: <%= 'アクセスキーを入力' %>
  secret_access_key: <%= 'セキュリティーキーを入力' %>
  region: <%= 'バケットのリージョン' %>
  bucket: <%= 'バケット名' %>
```
- configディレクトリの中のenvironmentsディレクトリの中にあるdevelopment.rbファイルを編集します。
```ruby
# デフォルトではlocalになっているので変更する
  config.active_storage.service = :amazon
```
ここまででaws s3に画像をアップロードすることができるようになりました。
最後にダイレクトアップロードができるようにしていきます。

##### 8, ダイレクトアップロードに変更する
app/assets/javascripts/application.jsに以下のコードを追加します。
```js
//= require activestorage
```
投稿フォームを以下のように編集します。
```erb
<%= form.file_field :attachments, multiple: true, direct_upload: true %>
```
最後にS3のCORS設定のURLを変更します。ローカルの場合は以下のようになります。
本番環境でダイレクトアップロードを使用する場合はここのURLを変更するようにしてください。
```
[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "PUT"
        ],
        "AllowedOrigins": [
            "http://localhost:3000"
        ],
        "ExposeHeaders": [
            "Origin",
            "Content-Type",
            "Content-MD5",
            "Content-Disposition"
        ],
        "MaxAgeSeconds": 3600
    }
]
```

以上でRailsのActive Storageを使ってS3に画像をダイレクトアップロードすることができます。
初学者なので間違った箇所があった場合は指摘して頂けると助かります。