# 機能設計書 39-自己アサイン

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるカードの「自己アサイン」機能について、その処理内容、入出力仕様、データベース操作を詳細に記述した機能設計書である。

### 本機能の処理概要

**業務上の目的・背景**：チームメンバーが自発的にタスクを引き受ける際、通常の担当者アサイン機能では自分自身を選択する手間がかかる。自己アサイン機能は、現在ログインしているユーザーを即座に担当者として割り当てることで、この操作を簡略化する。アジャイル開発などでチームメンバーが自律的にタスクを選択するワークフローに適している。

**機能の利用シーン**：チームメンバーがボード上のカードを確認し、自分で担当したいタスクを見つけた際にワンクリックで自分をアサインできる。カードアサイン画面で「Me」ボタン（ショートカットキー「m」）をクリックするか、カード詳細画面から直接操作可能。

**主要な処理内容**：
1. ユーザーが自己アサインをリクエスト
2. 対象カードのアクセス権を確認
3. toggle_assignmentでCurrent.userをアサイン/解除
4. アサインの場合は自動ウォッチとイベント記録

**関連システム・外部連携**：担当者アサイン機能（No.38）の特殊ケースとして実装。同じAssignableモジュールのtoggle_assignmentを使用する。

**権限による制御**：カードへのアクセス権を持つユーザーが自分自身をアサインできる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 22 | カードアサイン画面 | 主画面 | 「Me」ボタンのクリック、ショートカットキー「m」 |
| 19 | カード詳細画面 | 参照画面 | 自己アサインボタン（存在する場合） |

## 機能種別

CRUD操作（CREATE / DELETE トグル）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| card_id | String | Yes | 対象カードの番号（number） | Current.userがアクセス可能なカードであること |

### 入力データソース

- URLパスパラメータ: card_idはルーティングから取得
- 認証情報: Current.userから現在のログインユーザーを自動的にアサイン対象として使用

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| turbo_stream | HTML | カードメタ情報の差し替え用HTML（Turbo Stream形式） |
| HTTP Status | Integer | 成功時: 200 OK（turbo_stream）/ 204 No Content（JSON） |

### 出力先

- 画面表示: Turbo Streamによるカードメタ情報の部分置換
- JSON API: no_content（ボディなし）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POST /cards/:card_id/self_assignment

2. 認証・認可チェック（ApplicationController）
   └─ Current.userの設定、ログイン状態確認

3. カード取得（CardScoped#set_card）
   └─ Current.user.accessible_cards.find_by!(number: params[:card_id])
   └─ アクセス権がない場合は404エラー

4. ボード設定（CardScoped#set_board）
   └─ @board = @card.board

5. 自己アサイン処理（Card::Assignable#toggle_assignment）
   └─ @card.toggle_assignment(Current.user)
   └─ トグル動作: アサイン済みなら解除、未アサインならアサイン

6. レスポンス生成
   └─ turbo_stream: cards/assignments/createを使用
   └─ json: 204 No Content
```

### フローチャート

```mermaid
flowchart TD
    A[POST /cards/:card_id/self_assignment] --> B[認証チェック]
    B --> C{ログイン済み?}
    C -->|No| D[401 Unauthorized]
    C -->|Yes| E[カード取得]
    E --> F{アクセス権あり?}
    F -->|No| G[404 Not Found]
    F -->|Yes| H[toggle_assignment実行]
    H --> I{既に自分がアサイン済み?}
    I -->|Yes| J[unassign実行]
    I -->|No| K[assign実行]
    J --> L[イベント記録: unassigned]
    K --> M[Assignment作成]
    M --> N[watch_by実行]
    N --> O[イベント記録: assigned]
    L --> P[レスポンス生成]
    O --> P
    P --> Q{リクエスト形式}
    Q -->|turbo_stream| R[メタ情報差し替え]
    Q -->|json| S[204 No Content]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-39-01 | トグル動作 | 自分がアサイン済みの場合は解除、未アサインの場合は割り当て | toggle_assignment |
| BR-39-02 | 自動ウォッチ | 自己アサインした場合は自動的にウォッチ状態になる | assign時にwatch_by |
| BR-39-03 | 上限適用 | 担当者アサインと同じ上限（100人）が適用される | Assignment::LIMIT |
| BR-39-04 | 共通ビュー | レスポンスは通常のアサイン機能と同じビューを使用 | cards/assignments/create |

### 計算ロジック

特になし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 自己アサイン | assignments | INSERT | 担当者として自分を記録 |
| 自己アサイン解除 | assignments | DELETE | 担当者から自分を削除 |
| ウォッチ | watches | INSERT/UPDATE | アサイン時に自動ウォッチ |
| イベント | events | INSERT | アサイン/解除イベントを記録 |
| カードタッチ | cards | UPDATE | updated_atの更新 |

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

#### assignments

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | UUIDを自動生成 | 主キー |
| INSERT | account_id | card.account_id | カード経由で取得 |
| INSERT | card_id | 対象カードのID | 外部キー |
| INSERT | assignee_id | Current.user.id | 自分自身 |
| INSERT | assigner_id | Current.user.id | 自分自身（自己アサインのため） |
| DELETE | - | assignee_id = Current.user | 解除時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 未ログイン状態でアクセス | ログイン画面へリダイレクト |
| 404 | Not Found | 存在しないカード、またはアクセス権なし | エラーページ表示 |
| 422 | Unprocessable Entity | アサイン上限（100人）に達した | エラーメッセージ表示 |

### リトライ仕様

リトライ処理は不要。トグル動作のため、重複リクエストは逆操作になる点に注意。

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

担当者アサイン機能と同様。assignメソッド内ではAssignment作成、watch_by、イベント記録が順次実行されるが、明示的なトランザクションはない。

## パフォーマンス要件

- レスポンス時間: 200ms以内
- 単一ユーザー操作のため、パフォーマンス上の特別な考慮は不要

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

- 認証: ApplicationControllerのbefore_actionでログイン状態を確認
- 認可: accessible_cardsにより、アクセス可能なカードのみに操作を限定
- CSRF対策: Railsの標準CSRF保護機構を使用

## 備考

自己アサインは担当者アサイン機能の簡易版であり、内部的には同じtoggle_assignmentメソッドを呼び出している。assignee_idパラメータが不要な点が主な違い。

---

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

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

### 推奨読解順序

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

自己アサインは担当者アサインと同じデータ構造を使用する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | assignment.rb | `app/models/assignment.rb` | Assignmentモデル（担当者アサインと共通） |
| 1-2 | assignable.rb | `app/models/card/assignable.rb` | toggle_assignmentメソッドの定義（担当者アサインと共通） |

**読解のコツ**: 自己アサインでは`assignee_id`と`assigner_id`が同一（Current.user）になる点に注目。

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

コントローラのcreateアクションがエントリーポイントとなる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | self_assignments_controller.rb | `app/controllers/cards/self_assignments_controller.rb` | createアクションの実装 |
| 2-2 | card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | カード取得とボード設定のconcern |

**主要処理フロー**:
1. **5行目**: `@card.toggle_assignment(Current.user)`でトグル処理を実行
2. **7, 11行目**: `render "cards/assignments/create"`で通常のアサインと同じビューを使用

#### Step 3: ビューの共有を理解する

自己アサインは担当者アサインと同じビューを使用する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create.turbo_stream.erb | `app/views/cards/assignments/create.turbo_stream.erb` | 共通のTurbo Streamレスポンス |
| 3-2 | new.html.erb | `app/views/cards/assignments/new.html.erb` | 「Me」ボタンの定義（20-23行目） |

**主要処理フロー**:
- **20-23行目**: Current.userに対するアサインボタンにショートカットキー「m」が設定

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

```
Cards::SelfAssignmentsController#create
    │
    ├─ CardScoped#set_card
    │      └─ Current.user.accessible_cards.find_by!(number:)
    │
    ├─ CardScoped#set_board
    │      └─ @card.board
    │
    ├─ Card#toggle_assignment(Current.user) (Card::Assignable)
    │      │  ※ 担当者アサインと同じメソッドを使用
    │      │
    │      ├─ assigned_to?(Current.user)
    │      │
    │      ├─ [自分がアサイン済みの場合] unassign(Current.user)
    │      │      ├─ assignments.destroy_by(assignee: Current.user)
    │      │      └─ track_event(:unassigned)
    │      │
    │      └─ [未アサインの場合] assign(Current.user)
    │             ├─ assignments.create(assignee: Current.user, assigner: Current.user)
    │             ├─ watch_by(Current.user)
    │             └─ track_event(:assigned)
    │
    └─ render "cards/assignments/create"
           └─ turbo_stream.replace meta
```

### データフロー図

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

card_id      ───▶ CardScoped#set_card    ───▶ @card
(URLパラメータ)       │
                     │
Current.user ───────▶│
(自動取得)           │
                     ▼
               Card::Assignable#toggle_assignment
                     │
                     ├─ [assign] ───────────▶ assignmentsテーブル
                     │      │                 (INSERT: assignee=assigner=Current.user)
                     │      ├───────────────▶ watchesテーブル
                     │      │                 (INSERT/UPDATE)
                     │      └───────────────▶ eventsテーブル
                     │                        (INSERT)
                     │
                     └─ [unassign] ─────────▶ assignmentsテーブル
                            │                 (DELETE)
                            └───────────────▶ eventsテーブル
                                              (INSERT)
                     │
                     ▼
               render "cards/assignments/create"
                                       ───▶ Turbo Stream HTML
                                            (メタ情報更新)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| self_assignments_controller.rb | `app/controllers/cards/self_assignments_controller.rb` | コントローラ | HTTPリクエストの処理 |
| card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | コントローラconcern | カード取得の共通処理 |
| assignable.rb | `app/models/card/assignable.rb` | モデルconcern | アサインのビジネスロジック（共通） |
| assignment.rb | `app/models/assignment.rb` | モデル | アサイン状態を表すActiveRecordモデル（共通） |
| card.rb | `app/models/card.rb` | モデル | カードの基底モデル |
| create.turbo_stream.erb | `app/views/cards/assignments/create.turbo_stream.erb` | ビュー | Turbo Streamレスポンス（共通） |
| new.html.erb | `app/views/cards/assignments/new.html.erb` | ビュー | アサイン画面（Meボタンの定義） |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（95行目） |
| schema.rb | `db/schema.rb` | スキーマ | assignmentsテーブル定義（共通） |
