さあ、アップロードしよ？

## はじめに
SPA開発でBackend側をRails apiモードを使って画像をアップロードする機能を実装していたのですがそもそも画像を登録するという点に知識が浅く理解をしていなかったのでアウトプットすることを目的で記事にしました

### ER図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2879698/bac46710-486f-4fae-9e62-71f3035f8ef8.png)

当初のテーブルの関係性はER図のように1つの商品には複数の画像を登録できるように設計していました

### 商品の登録
```ruby:products_controller.rb
class Api::V1::ProductsController < ApplicationController
  def create
    product = Product.new(prudct_params)

    if product.save
      if params[:product][:images].present?
        params[:product][:images].each do |image|
          product.product_images.create(image: image)
        end
      end

      render json: puroduct, include: [:product_images], status: :created
    else
      render json: { status: "error", errors: product.errors.full_messages }, status: :unprocessable_entity
    end
  end

  private

  def product_params
    params.require(:product).permit(:name, :price, :description)
  end
end
```

商品を登録するコードを書き終えたのでいざ、データを注入だ！と思ったのですが画像は登録できませんでした(厳密に言えば登録はできるがアップロードはできない)

### なぁぜなぁぜ？
そもそも、product_imagesのimageカラムには画像のファイルパスやURLが保存される仕様になっているからでした...ズコーッ!

### Active Storage
Active Storageとは、Railsに標準で組み込まれている画像・ファイル管理システムである

#### 実装方法
##### 1. インストール
```bash:bash
# Active Storageをインストール
rails active_storage:install
rails db:migrate
```
このコマンドを実行することで`active_storage_blobs`と`active_storage_attachments`というテーブルが作成される

##### 2. モデルに`has_one_attached`を追加
ProductImageモデルに`has_one_attached`を設定
```ruby:product_image.rb
class ProductImage < ApplicationRecord
  belongs_to :product
  has_one_attached :image
end
```
##### 3. コントローラーの修正
Postmanやフロントエンドからmultipart/form-data形式で画像を送信できるようにする
```ruby:products_controller.rb
class Api::V1::ProductsController < ApplicationController
  def create
    product = Product.new(product_params)

    if product.save
      if params[:product][:images].present?
        params[:product][:images].each do |image|
          product_image = product.product_images.create
          product_image.image.attach(image)
        end
      end

      render json: product, include: [:product_images => { methods: :image_url }], status: :created
    else
      render json: { errors: product.errors.full_messages }, status: :unprocessable_entity
    end
  end

  private

  def product_params
    params.require(:product).permit(:name, :price, :description, images: [])
  end
end
```

##### 4. 画像のURLを取得
モデルにimage_urlメソッドを追加し、APIレスポンスで画像URLを返す

```ruby:product_image.rb
class ProductImage < ApplicationRecord
  belongs_to :product
  has_one_attached :image

  def image_url
    Rails.application.routes.url_helpers.url_for(image) if image.attached?
  end
end
```

##### 5. Postmanでテスト
Postmanにてmultipart/form-data形式で画像を送信
1. POST http://localhost:8000/api/v1/products
2. Bodyタブを開く
3. form-dataを選択
4. product[images][]に画像ファイルを追加

以上が画像をアップロードをする流れです

### まだ、終わりません！もっと詳しく！
ここで、疑問に思ったのだが**active_storage_blobsテーブルに保存される**ならそもそも**pruduct_imagesテーブルは要らなかったのか？**

画像は**active_storage_blobs**にアップロードされるので**product_images**の**imageカラム**は「**画像のファイルパスを文字列として保存する想定の設計**」だったため不要になります。しかし、Active Storageを使うならproduct_imagesは単なる中間テーブルとして機能する形になる
なぜなら、1つの商品に複数の画像を登録するためである

* Active Storageを使ってproduct_imagesテーブルに画像をアップロードする
* **active_stotage_attachments**が**product_images**と**active_storage_blobs**を紐付ける

#### Active Storageのデータの流れ
Active Storageは以下の3つのテーブルを使って、画像を管理する

1. product_images → どの商品に紐づく画像なのかを管理(商品IDなど)
2. active_storage_blobs → 画像の実体データを管理
3. active_atorage_attachments → product_imagesとactive_storage_blobsを紐づける
4. この仕組みと使うことでproduct_imagesにhas_one_attached :imageを設定するだけで画像を管理できる

```plaintext:データの流れ図
               ┌────────────────────────┐
               │  product_images        │  ← 商品ごとの画像管理
               │ (id, commodity_crop_id)│
               └───────────┬────────────┘
                           │ (紐づけ)
                           ▼
               ┌────────────────────────────┐
               │ active_storage_attachments │  ← 関連付けテーブル
               │ (record_id, blob_id)       │
               └───────────┬────────────────┘
                           │ (紐づけ)
                           ▼
               ┌────────────────────────┐
               │  active_storage_blobs  │  ← 画像の実体データ
               │ (id, filename, data)   │
               └────────────────────────┘
```

#### データの関係性

|テーブル|	役割	|具体例|
|-------|-------|------|
|product_images| 商品ごとの画像情報	|商品ID: 1, 画像ID: 10|
|active_storage_attachments|	products_images と active_storage_blobs を関連付ける|	record_id: 10, blob_id: 5|
|active_storage_blobs|	実際の画像データ|	blob_id: 5, ファイル名: apple.jpg|

## まとめ
画像をアップロードするのにあたり、単にカラム持たせて画像を登録できるものだと思っていたので今回、いい勉強になったなーって思いました！
まだまだ、学習不足だけど新しいことに気づくと楽しいな！！