# 機能設計書 13-Active Storage

## 概要

本ドキュメントは、Ruby on Railsフレームワークにおけるファイルストレージフレームワーク「Active Storage」の機能設計を記載する。Active StorageはRailsアプリケーションにファイルを添付するための統一インターフェースを提供し、ローカルディスクおよびクラウドストレージサービスとの連携を実現する。

### 本機能の処理概要

Active Storageは、ファイルのアップロード、ダウンロード、変換、保存を管理するフレームワークである。Active Recordモデルにファイルを添付し、Amazon S3、Google Cloud Storage、Microsoft Azure Storageなどのクラウドサービスとシームレスに連携できる。

**業務上の目的・背景**：
Webアプリケーションでは、ユーザーがプロフィール画像、ドキュメント、動画などをアップロードする機能が必要となる。従来、ファイルアップロード機能はgem（CarrierWave、Paperclip等）に依存していたが、Active Storageの導入により、Railsフレームワーク標準でファイル管理機能を提供できるようになった。クラウドストレージとの統一されたインターフェースにより、開発効率が向上し、ストレージサービスの切り替えも容易になる。

**機能の利用シーン**：
- ユーザープロフィール画像のアップロード
- ドキュメント・PDFの添付
- 画像のリサイズ・サムネイル生成
- 動画・音声ファイルの管理
- 一時ファイルのダイレクトアップロード
- 複数ファイルの一括添付

**主要な処理内容**：
1. ファイルのアップロードとBlobの作成
2. Active Recordモデルへのファイル添付（has_one_attached/has_many_attached）
3. ダイレクトアップロード（クライアントからストレージへ直接アップロード）
4. 画像バリアント（リサイズ、クロップ等の変換）の生成
5. プレビュー画像の生成（PDF、動画等）
6. ファイルのダウンロードURL生成
7. ミラーリング（複数ストレージへの同時保存）
8. ファイルの分析（メタデータ抽出）

**関連システム・外部連携**：
- Amazon S3
- Google Cloud Storage
- Microsoft Azure Storage
- ローカルディスクストレージ
- ImageMagick/libvips（画像変換）
- FFmpeg（動画プレビュー）
- Active Job（非同期処理）

**権限による制御**：
- 署名付きURLによるアクセス制御
- トークンベースの認証（signed_id）
- ダイレクトアップロード時のCSRF保護

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 19 | メールフォーム作成画面 | 補助機能 | 添付ファイルのアップロード処理 |

## 機能種別

ファイル管理 / ストレージ連携 / 画像処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| io | IO | Yes | ファイルの入力ストリーム | - |
| filename | String | Yes | ファイル名 | - |
| content_type | String | No | MIMEタイプ | - |
| metadata | Hash | No | カスタムメタデータ | - |
| service_name | String/Symbol | No | 使用するストレージサービス名 | 設定済みサービス |
| checksum | String | No | MD5チェックサム（ダイレクトアップロード時） | Base64エンコード |
| byte_size | Integer | No | ファイルサイズ（ダイレクトアップロード時） | - |

### 入力データソース

- HTTPファイルアップロード（multipart/form-data）
- ダイレクトアップロード（クライアントJavaScript）
- サーバーサイドファイル読み込み

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| signed_id | String | 署名付き識別子 |
| key | String | ストレージ上のファイルキー |
| url | String | ファイルアクセスURL |
| filename | ActiveStorage::Filename | ファイル名オブジェクト |
| content_type | String | MIMEタイプ |
| byte_size | Integer | ファイルサイズ |
| checksum | String | MD5チェックサム |
| metadata | Hash | ファイルメタデータ |

### 出力先

- ストレージサービス（S3、GCS、Azure、ローカル）
- データベース（active_storage_blobs、active_storage_attachmentsテーブル）

## 処理フロー

### 処理シーケンス

```
1. ファイルアップロード
   └─ ActiveStorage::Blob.create_and_upload!
      └─ Blobレコード作成
      └─ ファイルをストレージにアップロード
      └─ チェックサム検証

2. モデルへの添付
   └─ has_one_attached / has_many_attached
      └─ Attachmentレコード作成
      └─ BlobとRecordの関連付け

3. ファイルアクセス
   └─ url / service_url
      └─ 署名付きURL生成
      └─ 有効期限付きアクセス許可

4. バリアント生成
   └─ variant(transformations)
      └─ 変換パラメータの検証
      └─ ImageProcessingによる画像変換
      └─ 変換済み画像の保存
```

### フローチャート

```mermaid
flowchart TD
    A[ファイル選択] --> B{アップロード方式}
    B -->|通常| C[サーバーにアップロード]
    B -->|ダイレクト| D[create_before_direct_upload]
    C --> E[Blob.create_and_upload!]
    D --> F[署名付きURL取得]
    F --> G[クライアントからストレージへ直接アップロード]
    G --> H[Blob完成通知]
    E --> I[Attachmentレコード作成]
    H --> I
    I --> J[モデルに添付完了]
    J --> K{表示時}
    K -->|元ファイル| L[service_url生成]
    K -->|バリアント| M[variant.processed]
    M --> N{処理済み?}
    N -->|No| O[画像変換実行]
    N -->|Yes| P[キャッシュから取得]
    O --> Q[変換済みファイル保存]
    Q --> L
    P --> L
    L --> R[リダイレクトまたはプロキシ]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | Blob不変性 | Blobは一度作成されると内容を変更不可 | 常時 |
| BR-002 | 一意キー生成 | Blobキーは36文字のBase36形式で生成 | 常時 |
| BR-003 | 署名付きURL有効期限 | デフォルト5分間有効 | service_urls_expire_in設定 |
| BR-004 | 外部キー制約 | Attachmentが存在するBlobは削除不可 | purge時 |

### 計算ロジック

**キー生成**：
```ruby
SecureRandom.base36(28)  # 28文字のBase36文字列
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| create_and_upload! | active_storage_blobs | INSERT | Blobレコード作成 |
| attach | active_storage_attachments | INSERT | 添付関連レコード作成 |
| purge | active_storage_blobs, active_storage_attachments | DELETE | レコード削除 |
| variant | active_storage_variant_records | INSERT | バリアントレコード作成（track_variants有効時） |

### テーブル別操作詳細

#### active_storage_blobs

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | key | SecureRandom.base36(28) | 一意キー |
| INSERT | filename | 元ファイル名 | |
| INSERT | content_type | MIMEタイプ | Marcel gemで判定 |
| INSERT | metadata | JSON形式 | analyzed, identified等 |
| INSERT | byte_size | ファイルサイズ | |
| INSERT | checksum | MD5 Base64 | |
| INSERT | service_name | ストレージサービス名 | |

#### active_storage_attachments

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | 添付名（例：avatar） | |
| INSERT | record_type | モデルクラス名 | ポリモーフィック |
| INSERT | record_id | モデルID | |
| INSERT | blob_id | Blob ID | 外部キー |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| IntegrityError | 例外 | チェックサム不一致 | 再アップロード |
| FileNotFoundError | 例外 | ストレージにファイルが存在しない | Blobの整合性確認 |
| InvalidForeignKey | 例外 | Attachmentが存在するBlobの削除 | Attachmentを先に削除 |
| UnprocessableEntity | HTTP 422 | ダイレクトアップロードパラメータ不正 | パラメータ確認 |

### リトライ仕様

- ストレージサービスへのアップロード失敗時は自動リトライなし
- Active Jobを使用した非同期処理は各ジョブのリトライ設定に依存

## トランザクション仕様

- Blobの作成とファイルアップロードは別トランザクション
- Attachmentの作成は親モデルのトランザクション内で実行
- purge時はトランザクション内でレコード削除、その後ファイル削除

## パフォーマンス要件

- ダイレクトアップロードの使用を推奨（サーバー負荷軽減）
- バリアントはオンデマンド生成または事前生成を選択可能
- 大量ファイルのN+1問題を避けるためwith_attached_*スコープを使用

## セキュリティ考慮事項

- 署名付きURLによる期限付きアクセス制御
- signed_idによるクライアントサイドでの安全な参照
- ダイレクトアップロード時のContent-Type検証
- content_types_to_serve_as_binaryによる危険なコンテンツの強制ダウンロード
- content_types_allowed_inlineによるインライン表示制限

## 備考

- Active StorageはRails 5.2で導入
- image_processing gemによる画像変換（MiniMagickまたはVips）
- Rails 7以降ではVariant Trackerによるバリアント管理が可能

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

Blob、Attachment、Variantの関係を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | blob.rb | `activestorage/app/models/active_storage/blob.rb` | Blobモデルの属性、クラスメソッド |
| 1-2 | attachment.rb | `activestorage/app/models/active_storage/attachment.rb` | Attachmentの関連定義 |
| 1-3 | variant.rb | `activestorage/app/models/active_storage/variant.rb` | バリアントの構造 |

**読解のコツ**: Blobはファイルのメタデータを保持し、Attachmentはモデルとの関連を管理する。Variantは変換済みファイルを表す。

#### Step 2: ファイルアップロード処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | blob.rb | `activestorage/app/models/active_storage/blob.rb` | create_and_upload!、unfurl、upload_without_unfurling |

**主要処理フロー**:
- **101-105行目**: `create_and_upload!`でBlobを作成しアップロード
- **254-259行目**: `unfurl`でチェックサム計算、コンテンツタイプ判定
- **261-263行目**: `upload_without_unfurling`でストレージにアップロード

#### Step 3: モデル添付を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | attached.rb | `activestorage/lib/active_storage/attached.rb` | has_one_attached/has_many_attachedの定義 |
| 3-2 | model.rb | `activestorage/lib/active_storage/attached/model.rb` | モデルマクロの実装 |

#### Step 4: ストレージサービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | service.rb | `activestorage/lib/active_storage/service.rb` | サービスインターフェース |
| 4-2 | s3_service.rb | `activestorage/lib/active_storage/service/s3_service.rb` | S3実装例 |
| 4-3 | disk_service.rb | `activestorage/lib/active_storage/service/disk_service.rb` | ローカルディスク実装 |

### プログラム呼び出し階層図

```
user.avatar.attach(io: file, filename: 'avatar.jpg')
    │
    └─ ActiveStorage::Attached::One#attach
           │
           └─ ActiveStorage::Attached::Changes::CreateOne
                  │
                  ├─ build_blob_from_attachment
                  │      │
                  │      └─ ActiveStorage::Blob.build_after_unfurling
                  │             │
                  │             └─ unfurl (checksum, content_type計算)
                  │
                  └─ save時
                         │
                         ├─ Attachment.create
                         │
                         └─ uploaded
                                │
                                ├─ blob.upload_without_unfurling
                                │      │
                                │      └─ service.upload
                                │
                                └─ analyze_blob_later
                                       │
                                       └─ ActiveStorage::AnalyzeJob
```

### データフロー図

```
[入力]                      [処理]                         [出力]

ファイルIO ───────▶ Blob.create_and_upload! ──────▶ active_storage_blobs
                          │
                          ├─▶ unfurl (メタデータ抽出)
                          │
                          └─▶ service.upload ─────────────▶ Storage Service
                                                               (S3/GCS/Disk)

Model.attach ─────▶ Attachment.create ────────────▶ active_storage_attachments
     │
     └─▶ after_commit: analyze_job


blob.url ─────────▶ service.url ──────────────────▶ 署名付きURL
     │
     └─▶ redirect_to / proxy


blob.variant ─────▶ Variant#process ──────────────▶ 変換済みファイル
                          │
                          └─▶ ImageProcessing
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| active_storage.rb | `activestorage/lib/active_storage.rb` | ソース | モジュール定義、設定 |
| blob.rb | `activestorage/app/models/active_storage/blob.rb` | モデル | Blobエンティティ |
| attachment.rb | `activestorage/app/models/active_storage/attachment.rb` | モデル | 添付関連エンティティ |
| variant.rb | `activestorage/app/models/active_storage/variant.rb` | モデル | バリアントエンティティ |
| variant_with_record.rb | `activestorage/app/models/active_storage/variant_with_record.rb` | モデル | バリアント（レコード追跡付き） |
| variation.rb | `activestorage/app/models/active_storage/variation.rb` | モデル | 変換パラメータ |
| preview.rb | `activestorage/app/models/active_storage/preview.rb` | モデル | プレビュー画像 |
| attached.rb | `activestorage/lib/active_storage/attached.rb` | ソース | 添付マクロ |
| model.rb | `activestorage/lib/active_storage/attached/model.rb` | ソース | モデル拡張 |
| one.rb | `activestorage/lib/active_storage/attached/one.rb` | ソース | 単一添付 |
| many.rb | `activestorage/lib/active_storage/attached/many.rb` | ソース | 複数添付 |
| service.rb | `activestorage/lib/active_storage/service.rb` | ソース | サービス基底クラス |
| s3_service.rb | `activestorage/lib/active_storage/service/s3_service.rb` | ソース | S3サービス |
| gcs_service.rb | `activestorage/lib/active_storage/service/gcs_service.rb` | ソース | GCSサービス |
| azure_storage_service.rb | `activestorage/lib/active_storage/service/azure_storage_service.rb` | ソース | Azureサービス |
| disk_service.rb | `activestorage/lib/active_storage/service/disk_service.rb` | ソース | ディスクサービス |
| mirror_service.rb | `activestorage/lib/active_storage/service/mirror_service.rb` | ソース | ミラーサービス |
| analyze_job.rb | `activestorage/app/jobs/active_storage/analyze_job.rb` | ジョブ | 分析ジョブ |
| purge_job.rb | `activestorage/app/jobs/active_storage/purge_job.rb` | ジョブ | 削除ジョブ |
| transform_job.rb | `activestorage/app/jobs/active_storage/transform_job.rb` | ジョブ | 変換ジョブ |
| direct_uploads_controller.rb | `activestorage/app/controllers/active_storage/direct_uploads_controller.rb` | コントローラ | ダイレクトアップロード |
| blobs_controller.rb | `activestorage/app/controllers/active_storage/blobs/` | コントローラ | Blobアクセス |
| representations_controller.rb | `activestorage/app/controllers/active_storage/representations/` | コントローラ | バリアントアクセス |
