# 機能設計書 49-アバター管理

## 概要

本ドキュメントは、Fat Free CRMのアバター管理機能（UsersController#avatar, #upload_avatar）の設計仕様を定義するものである。

### 本機能の処理概要

アバター管理機能は、ユーザーのプロファイルアバター画像をアップロード・変更する機能である。独自の画像をアップロードするか、Gravatar（グローバルアバター）を使用するかを選択できる。

**業務上の目的・背景**：CRMシステムにおいて、ユーザーを視覚的に識別できることは重要である。アバター画像により、コメント作成者の識別やユーザー一覧での視認性向上が図れる。Gravatarとの連携により、他サービスでも使用している統一アバターを利用可能。

**機能の利用シーン**：ユーザーが自分のプロファイルページで「画像をアップロード」リンクをクリックし、プロファイル画像を設定・変更する場面で利用される。新規ユーザーの初期設定、アバター画像の更新などに使用される。

**主要な処理内容**：
1. 【avatar】アバターアップロードフォームを表示
2. 【upload_avatar】アップロードされた画像を保存またはGravatarに切り替え
3. プロファイル表示を更新

**関連システム・外部連携**：Gravatar（外部サービス）

**権限による制御**：CanCanCanで権限管理。自分のアバターまたは管理者のみ変更可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 32 | ユーザー詳細画面（プロフィール） | 主画面 | アバター画像のアップロード/変更 |

## 機能種別

ファイルアップロード処理 / CREATE・UPDATE操作

## 入力仕様

### 入力パラメータ

#### avatarアクション

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 対象ユーザーのID | 存在するユーザーであること |
| cancel | String | No | キャンセルフラグ | "true"でキャンセル処理 |

#### upload_avatarアクション

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 対象ユーザーのID | 存在するユーザーであること |
| gravatar | String | No | Gravatar使用フラグ | "1"でGravatar使用 |
| avatar[image] | File | No | アップロード画像ファイル | 画像ファイル（PNG/JPEG/GIF） |

### 入力データソース

- URLパラメータ：ユーザーID
- 画面入力：画像ファイルまたはGravatar選択

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| @user | User | 対象ユーザーオブジェクト |
| @avatar | Avatar | 作成されたアバターオブジェクト |

### 出力先

- データベース：avatarsテーブルへINSERT/DELETE
- ファイルストレージ：Active Storageで画像保存
- 画面表示：AJAXレスポンスでプロファイル表示更新

## 処理フロー

### 処理シーケンス

```
【avatarアクション】
1. ユーザー取得
   └─ load_and_authorize_resourceで自動取得・権限チェック

2. キャンセル処理判定
   ├─ cancel=true: アップロードフォームを閉じる
   └─ cancel=false: アップロードフォームを表示

【upload_avatarアクション】
1. ユーザー取得
   └─ load_and_authorize_resourceで自動取得・権限チェック

2. 処理分岐
   ├─ gravatar=1の場合
   │      └─ アバターをnilに設定（Gravatar使用）
   └─ 画像アップロードの場合
          └─ Avatar.createで新規アバター作成
          └─ バリデーションチェック
          └─ ユーザーにアバターを紐付け

3. レスポンス生成
   └─ upload_avatar.js.hamlでJavaScript応答
```

### フローチャート

```mermaid
flowchart TD
    subgraph avatarアクション
        A1[フォーム表示リクエスト] --> B1[権限チェック・ユーザー取得]
        B1 --> C1{権限あり?}
        C1 -->|No| D1[アクセス拒否]
        C1 -->|Yes| E1{cancel=true?}
        E1 -->|Yes| F1[フォームを閉じる]
        E1 -->|No| G1[アップロードフォーム表示]
    end

    subgraph upload_avatarアクション
        A2[アップロードリクエスト] --> B2[権限チェック・ユーザー取得]
        B2 --> C2{権限あり?}
        C2 -->|No| D2[アクセス拒否]
        C2 -->|Yes| E2{gravatar=1?}
        E2 -->|Yes| F2[アバターをnil設定]
        E2 -->|No| G2{画像あり?}
        G2 -->|Yes| H2[Avatar.create]
        H2 --> I2{バリデーション成功?}
        I2 -->|Yes| J2[ユーザーにアバター紐付け]
        I2 -->|No| K2[エラー表示]
        G2 -->|No| L2[何もしない]
        F2 --> M2[プロファイル更新表示]
        J2 --> M2
        K2 --> N2[フォーム再表示]
    end
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-49-01 | 権限制御 | 自分のアバターまたは管理者のみ変更可能 | 常時 |
| BR-49-02 | Gravatar切替 | gravatar=1パラメータでアバターをnilにしてGravatar使用 | Gravatar選択時 |
| BR-49-03 | 画像形式 | PNG、JPEG、GIF形式のみ受付 | アップロード時 |
| BR-49-04 | iframe対応 | アップロードはiframe経由で実行（responds_to_parent使用） | 画像アップロード時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ユーザー取得 | users | SELECT | 対象ユーザー取得 |
| アバター作成 | avatars | INSERT | 新規アバターレコード作成 |
| アバター削除 | avatars | DELETE | 既存アバター削除（Gravatar切替時） |
| ユーザー更新 | users | UPDATE | avatar関連更新 |
| 画像保存 | active_storage_blobs | INSERT | Active Storageで画像データ保存 |

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

#### avatars

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | user_id | current_user.id | アップロード者 |
| INSERT | entity_id | @user.id | 関連ユーザーID |
| INSERT | entity_type | "User" | ポリモーフィック型 |
| INSERT | image | Active Storage | 画像データ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | RecordNotFound | 対象ユーザーが存在しない | 404エラー応答 |
| - | CanCan::AccessDenied | アクセス権限なし | 403エラー応答 |
| - | バリデーションエラー | 画像形式不正 | エラーメッセージ表示 |

### リトライ仕様

リトライ不要（ユーザーが再アップロード）

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

アバター作成とユーザー更新は個別のトランザクションで実行

## パフォーマンス要件

- アップロード処理は2秒以内に応答（ファイルサイズ依存）

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

- 認証必須：ApplicationControllerで認証チェック
- 権限管理：CanCanCanで詳細な権限制御
- ファイル検証：画像ファイルの形式チェック
- XSS対策：ビューでのエスケープ処理

## 備考

- Active Storageを使用して画像を保存
- Gravatarはメールアドレスベースで外部から取得
- アップロードはiframe経由（responds_to_parent gem使用）

---

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

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

### 推奨読解順序

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

アバターモデルの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | avatar.rb | `app/models/polymorphic/avatar.rb` | スキーマ情報（9-21行目）、Active Storage（27行目） |

**読解のコツ**: AvatarモデルはActive Storage（has_one_attached :image）を使用し、entityポリモーフィック関連でUserやContactなどに紐付く。

#### Step 2: エントリーポイントを理解する

avatar/upload_avatarアクションの処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | users_controller.rb | `app/controllers/users_controller.rb` | avatarアクション（43-45行目） |
| 2-2 | users_controller.rb | `app/controllers/users_controller.rb` | upload_avatarアクション（50-69行目） |
| 2-3 | users_controller.rb | `app/controllers/users_controller.rb` | avatar_paramsメソッド（152-158行目） |

**主要処理フロー**:
1. **51行目**: gravatar=1の場合、アバターをnilに設定
2. **56-57行目**: Avatar.createで新規アバター作成
3. **58-62行目**: バリデーション結果に応じてアバター設定
4. **65-67行目**: responds_to_parentでiframe経由のレスポンス

#### Step 3: ビューレスポンスを理解する

JavaScript応答の生成を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | avatar.js.haml | `app/views/users/avatar.js.haml` | フォーム表示/キャンセル処理（1-9行目） |
| 3-2 | upload_avatar.js.haml | `app/views/users/upload_avatar.js.haml` | アップロード後の表示処理（1-6行目） |
| 3-3 | _avatar.html.haml | `app/views/users/_avatar.html.haml` | アップロードフォームパーシャル |

**主要処理フロー**:
- **avatar.js.haml 1-3行目**: cancel=trueでフォームを閉じる
- **avatar.js.haml 5-9行目**: フォーム表示、他のフォームを非表示
- **upload_avatar.js.haml 1-4行目**: 成功時にプロファイル再表示
- **upload_avatar.js.haml 5-6行目**: エラー時にフォーム再表示

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

```
UsersController#avatar
    │
    ├─ load_and_authorize_resource (CanCanCan)
    │      ├─ User.find
    │      └─ authorize! :avatar, @user
    │
    └─ respond_with(@user)
           └─ avatar.js.haml
                  └─ _avatar.html.haml

UsersController#upload_avatar
    │
    ├─ load_and_authorize_resource (CanCanCan)
    │      ├─ User.find
    │      └─ authorize! :upload_avatar, @user
    │
    ├─ params[:gravatar] の場合
    │      └─ @user.avatar = nil
    │
    ├─ params[:avatar] の場合
    │      ├─ Avatar.create(avatar_params)
    │      └─ @user.avatar = @avatar
    │
    └─ responds_to_parent
           └─ upload_avatar.js.haml
```

### データフロー図

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

【avatar】
ユーザーID ───▶ UsersController#avatar ───▶ JavaScript応答
                    │                          └─ フォーム表示
                    └─ 権限チェック

【upload_avatar】
画像ファイル ───▶ UsersController#upload_avatar ───▶ データベース
(or Gravatar)        │                                 ├─ avatars
                      ├─ 権限チェック                    └─ active_storage_blobs
                      ├─ Gravatar/画像判定
                      └─ アバター処理              ───▶ JavaScript応答
                                                        └─ プロファイル更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| users_controller.rb | `app/controllers/users_controller.rb` | コントローラー | avatar/upload_avatarアクション |
| avatar.rb | `app/models/polymorphic/avatar.rb` | モデル | Avatarモデル |
| user.rb | `app/models/users/user.rb` | モデル | Userモデル（avatar関連） |
| avatar.js.haml | `app/views/users/avatar.js.haml` | ビュー | フォーム表示JavaScript |
| upload_avatar.js.haml | `app/views/users/upload_avatar.js.haml` | ビュー | アップロード後JavaScript |
| _avatar.html.haml | `app/views/users/_avatar.html.haml` | ビュー | アップロードフォームパーシャル |
| routes.rb | `config/routes.rb` | 設定 | ルーティング設定（157-159行目） |
