【Rails】Active Storage の役割をざっくりイメージしてみる

# はじめに
はじめまして🏃
自分は現在プログラミングスクールでRuby on Railsを中心に勉強中の者です。
今回は学習する中で Active Storage が何をしてくれるのかを理解するのに苦労したので、初学者なりにまずは全体のイメージを掴むために整理してみました。

:::note warn
今回は全体のイメージ（流れ）をざっくり掴むのが目的なので、読んでて「補足したい・・！」と思われる部分が多くあると思いますが、そこはご了承ください・・・🙏
また、自分がプログラミング初学者のため、内容に誤りがあれば優しくコメントで教えてください🙇
:::

# 概要

🔸Active Storageとは何者？
🔸Active Storageの導入方法
🔸Active Storageが提供する3つのテーブルについて（今回のメイン）
🔸説明しきれなかったこと
🔸まとめ

# 🔸Active Recode とは何者？

Railsガイドの説明はこう。
> Active Storageは、Amazon S3、Google Cloud Storage、Microsoft Azure Storageなどのクラウドストレージサービスへのファイルのアップロードや、ファイルをActive Recordオブジェクトにアタッチする機能を提供します。 development環境とtest環境向けのローカルディスクベースのサービスを利用できるようになっており、ファイルを下位のサービスにミラーリングしてバックアップや移行に用いることも可能です。
Active Storageは、アプリケーションにアップロードした画像の変形や、PDFや動画などの画像以外のアップロードファイルの内容を代表する画像の生成、任意のファイルからのメタデータ抽出にも利用できます
> 


**分解してものすごくざっくり解釈するとこんな感じ💪**

:::note
- ファイルをクラウドストレージやローカルディスクにアップロードできる。
開発環境やテスト環境の時 ： ローカルディスク（自分のPC等）にファイルをアップロード。
本場環境の時： クラウドストレージサービスにファイルをアップロード。
- データ（ActiveRecordモデル）にファイルをアタッチ（添付・紐付け）できる。
- アップロードした画像の変形もできる。
- アップロードしたPDFや動画から代表画像を作れる。
- アップロードしてファイルからメタデータ（ファイルの詳細情報）を取り出せる。
:::
[Railsガイド：Active Storage ](https://railsguides.jp/active_storage_overview.html)

# 🔸Active Storageの導入方法

以下の流れで整理していきます！
１、DBにテーブル作成
２、ファイルの保存先のストレージ設定
３、ファイルをモデル（レコード）に添付する

### １、DBにテーブル作成

Active StorageはRails 5.2以降のバージョンに標準で組み込まれており、コマンドラインで以下の操作をすることで導入できる。

まずはActiveStorageに必要なマイグレーションを生成する。

```
rails active_storage:install
```

これによってActiveStorageが使用する3つのテーブルを作成するマイグレーションが生成される。（各テーブルの用途は後ほど説明します）

- `active_storage_blobs`
- `active_storage_variant_records`
- `active_storage_attachments`

次に、マイグレーションを実行してDBに反映（先ほどの3つのテーブルを作成）

```
rails db:migrat

# 以下の3つのテーブルが生成される。
== 20210811020333 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs, {})
   -> 0.1982s
-- create_table(:active_storage_attachments, {})
   -> 0.0799s
-- create_table(:active_storage_variant_records, {})
   -> 0.0805s
== 20210811020333 CreateActiveStorageTables: migrated (0.3589s) ===============
```

### ２、ファイル保存先のストレージを設定

Active Storageのサービスは`config/storage.yml`で宣言する。
**「アプリケーションが使うサービス」** ごとに、名前と必要な構成を指定する。
例えば以下の場合では、`local`、`test`、`amazon`という3つのサービスを宣言している。

```ruby
# config/storage.yml

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

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  bucket: ""
  region: "" # 例: 'ap-northeast-1'
```
:::note
Disk（ローカルストレージ）や、AWSの`S3`、googleの`GCS`、microsoftの`AzureStorageS3 等のクラウドストレージサービスの各詳細については割愛しますのでRailsガイドを参照ください🙇
クラウドストレージサービスを使う場合はgemの追加が必要のようです。
[Railsガイド：組み込みのサービスアダプタ](https://railsguides.jp/active_storage_overview.html#disk%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9)
![スクリーンショット 2023-10-04 9.32.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478709/7aa53af9-7fca-a7e5-7cb0-f055643edb79.png)

:::

</aside>

次に、先ほど設定した利用サービスをActive Storageに認識させるため、`Rails.application.config.active_storage.service`を設定していく必要がある。
 使うサービスは環境ごと **（開発・テスト・本番）** に異なることもあるため、この設定を環境ごとに行うのが良いらしい。

- **development環境** でローカルDiskサービスを使うには、`config/environments/development.rb`に以下を追加！

```ruby
# ファイルをローカルのディスクに保存する
config.active_storage.service = :local
```

- **test環境** でtestサービスを利用するには、`config/environments/test.rb`に以下を追加！

```ruby
# ローカルファイルシステム上のアップロード済みファイルを一時ディレクトリに保存する
config.active_storage.service = :test
```

- **production環境** でAmazon S3を利用するには、`config/environments/production.rb`に以下を追加！

```ruby
# ファイルをAmazon S3に保存する
config.active_storage.service = :amazon
```


### ３、ファイルをモデル（レコード）に添付する

ActiveStorageを使って画像などのデータを既存のテーブルと紐づけるために、モデルファイルに紐付け関連を追加する！
**「1つのファイルを追加する場合」** と **「複数のファイルを追加する場合」** で分けて整理してみる。

:::note
ここでは添付する基本的な流れだけ整理するので、他の細かなオプションについては説明割愛します。実際はセキュリティ面から **「ファイルのサイズ」** や **「ファイルの形式」** 等のバリデーション制限を設けることを検討する必要があります！
詳細はRailsガイドを見てください🙇
[Railsガイド：ファイルをレコードに添付する](https://railsguides.jp/active_storage_overview.html#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E6%B7%BB%E4%BB%98%E3%81%99%E3%82%8B)
:::

### 「1つのファイルを追加する場合」

**has_one_attached** マクロは、レコードとファイルの間に1対1のマッピングを設定する。
つまり、レコード1件ごとに1個のファイルを添付できる。

例えば、アプリケーションに`User`モデルがあるとして、
このUserモデルにuserアバター画像を添付したい場合は、以下のように`User`モデルを定義ればいい。

```ruby
class User < ApplicationRecord
  has_one_attached :avatar
end
```

ビューのフォームを作成し画像を選択 → 登録or更新

```ruby
<%=** form.file_field :avatar %>
```

コントローラーでフォームからのデータ（今回だとアバター画像ファイル）を受け取る。

```ruby
class SignupController< ApplicationController
  def create
    @user= User.create!(user_params)
    session[:user_id]= user.id
    redirect_to root_path
  end

  private
    def user_params
      params.require(:user).permit(:email_address, :password, :avatar)
    end
end
```

image_tagを使って画像を表示。

```ruby
<%= image_tag @user.avatar %>
```

### **「複数のファイルを追加する場合」**

**has_many_attached** マクロは、レコードとファイルの間に1対多の関係を設定する。
つまり、レコード1件ごとに多数の添付ファイルを添付できる。

例えば、アプリケーションに`Message`モデルがあるとして、
このMessageモデルに多数の画像を持たせるには、次のような`Message`モデルを定義すればいい.

```ruby
class Message < ApplicationRecord
  has_many_attached :images
end
```

ビューのフォームを作成し画像を選択 → 登録or更新

```ruby
<%= form.file_field :avatar, multiple: true %>

# multiple: trueオプションを付けることで複数ファイルの選択が可能になる。
```

コントローラーでフォームからのデータ（今回だとアバター画像ファイル）を受け取る。

```ruby
class MessagesController < ApplicationController
  def create
    @message = Message.create!(message_params)
    redirect_to message
  end
  
  private
    def message_params
      params.require(:message).permit(:title, :content, images: [])
    end
end

# 値の受け取りは（image: []）とする。
```

image_tagを使って画像を表示。

```ruby
<% @message.images.each do |image| %>
  <%= image_tag message.image %>
<% end %>
```

:::note
ここでのポイントとしては、ファイルを添付したいモデルのテーブルにカラムを追加する必要がないということです。
各モデルのテーブルに新たにカラムを追加することなく、ファイルを添付できるのがActiveStorageの1つのポイント！👏
:::

## 🔸Active Storageが提供する3つのテーブルについて
ここまで長くなりましたが、今回一番整理したかったのはこの部分です。🧐
ActiveStrageが提供する3つのテーブルについての用途がしっかり分かってなかったので、初学者なりにイメージをまとめてみました。

- `active_storage_blobs`
- `active_storage_attachments`
 - `active_storage_variant_records`

### `active_storage_blobs`

このテーブルは、アップロードされた各ファイルのメタデータを保存する。
ファイル名、コンテンツタイプ、ファイルサイズ、ファイルの実体への参照（実体は別のストレージサービスに保存される）なんかが含まれる。
カラムは**キー「key」、ファイル名「filename」、コンテンツタイプ「content_type」、ファイルサイズ「bute_size」、デジタル署名、作成時間**などがある。
キーは**ストレージサービスでのファイルの場所**を示す。

```ruby
create_table "active_storage_blobs", charset: "utf8mb4", force: :cascade do |t|
    t.string "key", null: false
    t.string "filename", null: false
    t.string "content_type"
    t.text "metadata"
    t.bigint "byte_size", null: false
    t.string "checksum"
    t.datetime "created_at", precision: nil, null: false
    t.string "service_name", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
  end
```

### `active_storage_attachments`

このテーブルは実際のアプリケーションのモデルとアップロードされたファイル(blob)を関連付ける**中間テーブル**のようなもの。
このテーブルによって、どのモデルがどのファイルを参照しているか「接続」の管理がされている。
name, record_type, record_id, blob_idなどのカラムがあり、これらを使って関連付けが行われる。
**record_typeカラム** には関連付けられているモデル（Active Record モデル）のクラス名が格納され、これによりどのモデルがどのファイルを使用しているかが識別される。

```ruby
create_table "active_storage_attachments", charset: "utf8mb4", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false   
    t.bigint "record_id", null: false
    t.bigint "blob_id", null: false
    t.datetime "created_at", precision: nil, null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
  end
```
![スクリーンショット 2023-10-04 9.35.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3478709/460a1e7e-6a54-9b04-d02e-19d05340b5b9.png)

:::note
ActiveRecordによってモデルのオブジェクト（上の図だとUserオブジェクト）がDB上のデータにマッピングされているように、Blobオブジェクトがローカルまたはクラウドストレージ上のファイルにマッピングされている。
このおかげで主モデル（上の図だとUser）, Attachment, Blobモデル間の関連付けにより、モデル（Userオブジェクト）と添付ファイルを紐付けることができるようになっている。🧐
:::

### `active_storage_variant_records`

このテーブルは元の画像から派生したバリアントの情報を保存する。
バリアントとは、元の画像から生成された異なるバージョンのことで、例えば、サムネイル画像やリサイズされた画像なんかが該当する。
Active Storageは元の画像を変更せずに、異なるサイズやフォーマットの画像バリアントを作成できる。
このテーブルは、そのバリアントの情報を保存する。
通常、アプリケーション内で直接操作する必要はありませんが、Active Storage によって内部的に使用される。

```ruby
create_table "active_storage_variant_records", charset: "utf8mb4", force: :cascade do |t|
    t.bigint "blob_id", null: false
    t.string "variation_digest", null: false
    t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
  end
```


## 🔸説明しきれなかった大事なこと

以下については触れてないので、またまたRailsガイドを見てください!🙇
全然まとめれてなくてすみません・・・🙇
[Railsガイド：active-storageについて](https://railsguides.jp/active_storage_overview.html#active-storage%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)

- ファイルに対するバリデーションについて。（セキュリティ面で必須）
- variantメソッドによる画像の変形（サイズ変更等）。
- purgeメソッドによるファイルの削除。
- downloadメソッドによるファイルのダウンロード
- analyze?メソッドによるファイルの解析
- representationメソッドによる画像、動画、PDFの表示
- その他

## 🔸まとめ
まだ自分でアプリを作成して採用するような作業まではできていないので、かなり大雑把なまとめになりましたが、全体的な機能のイメージはできた気はするので、実際に導入してみて慣れていきたいです。
しかし、調べていく中で「CarrierWave」と「Active Storage」のどちらが良いか？という意見があったので、導入する際には調査・検証して判断していく必要がありそうです。

### **【今回見てきたActiveStorageは以下の特徴】**

:::note
- モデルに新たなカラムを追加せずにファイルを添付できる。
- 開発環境、テスト環境、本番環境で異なるストレージサービスを利用することが容易になる。
- 画像変換や動画変換などの処理も行える。（今回ほぼ触れてませんが・・・）
:::

初学者にはRailsガイドを読むことに若干抵抗がありますが、「ここに基本の全てが書いてある！」と言う方も多いので、新しい技術に出会った際は逃げずに時間をかけてでも読み込んでいきたいです。

## 🔸参考

[Active Storage の概要 - Railsガイド](https://railsguides.jp/active_storage_overview.html#active-storageについて)
[Active Storageを使用したRailsにおけるファイルアップロードの方法 | みぎさんドットコム](https://migisanblog.com/rails-file-upload/#index_id1)
[Active Storageを使って画像をアップしよう！](https://pikawaka.com/rails/active_storage#1つのファイルを追加する)
[【Rails】ActiveStroageの使い方を実例で解説｜複数画像やファイルのアップロードと個別に削除・変更する方法](https://prograshi.com/framework/rails/active-storage/)
[[Rails5.2]ActiveStorageの仕組み（図あり）と使ってみてわかったこと - Qiita](https://qiita.com/eightfoursix/items/a47ce1bd945582f5d808)