はじめに
SPA開発でBackend側をRails apiモードを使って画像をアップロードする機能を実装していたのですがそもそも画像を登録するという点に知識が浅く理解をしていなかったのでアウトプットすることを目的で記事にしました
ER図
当初のテーブルの関係性はER図のように1つの商品には複数の画像を登録できるように設計していました
商品の登録
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. インストール
# Active Storageをインストール
rails active_storage:install
rails db:migrate
このコマンドを実行することでactive_storage_blobs
とactive_storage_attachments
というテーブルが作成される
2. モデルにhas_one_attached
を追加
ProductImageモデルにhas_one_attached
を設定
class ProductImage < ApplicationRecord
belongs_to :product
has_one_attached :image
end
3. コントローラーの修正
Postmanやフロントエンドからmultipart/form-data形式で画像を送信できるようにする
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を返す
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形式で画像を送信
- POST http://localhost:8000/api/v1/products
- Bodyタブを開く
- form-dataを選択
- 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つのテーブルを使って、画像を管理する
- product_images → どの商品に紐づく画像なのかを管理(商品IDなど)
- active_storage_blobs → 画像の実体データを管理
- active_atorage_attachments → product_imagesとactive_storage_blobsを紐づける
- この仕組みと使うことでproduct_imagesにhas_one_attached :imageを設定するだけで画像を管理できる
┌────────────────────────┐
│ 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 |
まとめ
画像をアップロードするのにあたり、単にカラム持たせて画像を登録できるものだと思っていたので今回、いい勉強になったなーって思いました!
まだまだ、学習不足だけど新しいことに気づくと楽しいな!!