AWS_S3+Ruby on Rails_Active Storageで画像データを保存

#概要
Railsでアプリケーションを作成する際、画像データをassetsのimagesフォルダへ保存させたりしますが、AWSとRailsのActive Storageを使用すれば、クラウドへ画像データを保存することができます。
私はアプリケーションをAWSを使用してデプロイすることを考えているので、Active Storageを使用してAWS S3へ保存する方法をまとめてみました。

#前提
* 本番環境にて動作する際、S3へ保存する。
* ユーザーモデルにて画像データを扱うものとする。

# セットアップ
* ImageMagickのインストール

Active Storageにて画像サイズを変更する機能があるImageMagickというソフトウェアがあるので事前にインストールしておきます。

```terminal:
$ brew install imagemagick
```

Gemfileを編集し、bundle installをします。

```ruby:gemfile
gem 'mini_magick', '~> 4.8'
```

```terminal:
$ bundle install
```

* マイグレーションの設定

まず初めにActive Storageをインストールし、active_storage_blobsテーブルとactive_atorage_attachmentテーブルを作成します。

```terminal:
$ bin/rails active_storage:install
$ bin/rails db:migrate
```

#モデルの編集
画像を保存したいモデルにクラスメソッドを追加します。
has_one_attachedメソッドは一つのファイルを保存したいときに使用します。
has_many_attachedメソッドは複数のファイルを保存したいときに使用します。
また、保存用、削除用としてattributeメソッドにてモデル属性を追加します。

```ruby:user.rb
has_one_attached :image
もしくは
has_many_attached :images

attribute :new_image
attribute :remove_image
```

attributeはモデルにて読み書き可能な属性として追加することができるメソッドです。
画像の保存方法はいろいろあると思いますが、私はモデル内にて保存処理を行う方法を取ったため、attributeメソッドにてモデル属性を追加しています。

モデル内にbefore_saveを追加します。

```ruby:user.rb
before_save do
  if new_image
    self.image = new_image
  elsif remove_image
    self.image.purge
  end
end
```

以上により画像ファイルがセーブされる際、new_imageがあれば、保存を行いremove_imageがあれば、ファイルの削除を行います。
ファイルの削除はpurgeメソッドを使います。

#viewの編集
フォームにファイルフィールドメソッドを設置します。

```:_form.erb
<%= form.label :new_image %>
<%= form.file_field :new_image %>
<% if @user.image.attached? %>
  <%= image_tag @user.image.variant(resize: "128x128") %>
  <%= form.check_box :remove_image %>
  <%= form.label :remove_image %>
<% end %>
```

画像ファイルがある場合は、チェックボックスにチェックを入れると削除できます。
image_tagのvariantメソッドは画像ファイルにのみ使えるメソッドで、画像ファイルの大きさを調整できます。

ここまで行うとActive Storageを使用してアプリケーションメソッド内のstorageディレクトリにファイルが保存されます。

#AWSの設定
続いてAWSにてS3バケットの作成を行います。
まずAWSアカウントを作成します。細かな設定方法は省略させて頂きますが、ルートユーザーを作成後IAMユーザーを作成し、IAMユーザーには適宜ポリシー(AmazonS3FullAccess)をアタッチします。このIAMユーザーのアクセスキーを作成し、アクセスキーIDとシークレットアクセスキーをcredentialsへ書き込みます。

```terminal:terminal
$ EDITOR="vim" rails credentials:edit
```

```:credentials.yml.enc
AWS:
  access_key_id: AWSのアクセスキー
  secret_access_key: AWSのシークレットアクセスキー
```

IAMユーザーにてバケットを作成します。
S3サービスを選択し、バケットメニューからバケットの作成を行います。
バケット名、リージョン等を設定し、バケットを作成します。

Gemfileにgemを追加しbundle installを行います。

```:Gemfile
gem 'aws-sdk-s3', require: false
```

続いて、config/storage.ymlを編集します。
amazon:と書かれた箇所のコメントアウトを外し、必要事項を記載します。

```yml:storage.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: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: S3のあるアベイラビリティゾーン
  bucket: バケット名
```

Rails.application.credentials.dig(:aws, :〜)にてcredentialsにて保存したアクセスキー等を呼び出します。

config/environments/production.rbファイルを編集します。

```ruby:production.rb
config.active_atorage.service = :amazon
```

以上で本番環境にてファイルをS3に保存することができます。

config/environments/development.rbファイルにて同様の箇所を編集すれば開発環境にてS3バケットに保存することができます。

#補足
has_many_attachedにて複数ファイルを扱う際はformの設定に注意が必要です。

```:_form.erb
<%= form.label :new_images %>
<%= form.file_field :new_images %>
<% if @user.image.attached? %>
  <% @user.images.each do |image| %>
  <%= image_tag @user.image.variant(resize: "128x128") %>
  <%= form.check_box :remove_image, {multiple:true}, image.id, false %>
  <%= form.label :remove_images %>
<% end %>
```

ファイルを削除する際はコントローラにて、処理を行いました。

```ruby:users_controller.rb
#省略
def update
#省略
  if params[:user][:remove_images]
    params[:user][:remove_images].each do |image_id|
      image = @user.images.find(image_id)
      image.purge
    end
  end
```

#まとめ
導入自体は簡単にできたので、とても便利でした。
複数ファイルを扱う際は注意が必要なので、かなりつまずきました。
AWSでファイルを扱う際は是非取り入れたいシステムのひとつです。

#参考文献
[RailsアプリでActiveStorage + AWS S3を使ってみよう！](https://qiita.com/daichi41/items/af2a56ea46c13ca55fd3)
[【Rails 5.2】 Active Storageの使い方](https://qiita.com/hmmrjn/items/7cc5e5348755c517458a#%E8%A4%87%E6%95%B0%E3%81%AE%E6%B7%BB%E4%BB%98%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E5%A0%B4%E5%90%88)