# 機能設計書 50-カードのリアクション削除

## 概要

本ドキュメントは、カードに付けたリアクションを削除する機能の設計を定義するものである。

### 本機能の処理概要

**業務上の目的・背景**：リアクションを誤って追加した場合や、感情が変わった場合に、自分が付けたリアクションを取り消せる機能は必要である。リアクション削除機能は、自分が付けたリアクションのみを削除可能とすることで、コミュニケーションの修正を可能にしつつ、他のユーザーのリアクションは保護する。

**機能の利用シーン**：
- 誤って付けたリアクションを取り消したいとき
- 別の絵文字に変えたいとき（削除して再追加）
- リアクションが不適切だと感じたとき

**主要な処理内容**：
1. DELETEリクエストで対象リアクションを指定
2. 対象カードおよびリアクションへのアクセス権限を確認
3. リアクターと現在のユーザーが一致することを確認
4. リアクションをデータベースから削除
5. Turbo StreamまたはJSONレスポンスで結果を返却

**関連システム・外部連携**：特になし（内部処理のみ）

**権限による制御**：
- カードへのアクセス権限を持つユーザーのみ操作可能
- リアクションの付与者（reacter）のみが削除可能（ensure_permission_to_administer_reaction）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 61 | リアクション追加画面 | 主機能 | カードに付けたリアクションを削除 |
| 19 | カード詳細画面 | 参照画面 | リアクション一覧から削除操作 |
| 60 | リアクション一覧画面 | 参照画面 | リアクションの一覧から削除操作 |

## 機能種別

CRUD操作（Delete）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| card_id | Integer | Yes | 対象カードのID（URLパラメータ） | 存在確認、アクセス権限確認 |
| id | UUID | Yes | 対象リアクションのID（URLパラメータ） | 存在確認 |

### 入力データソース

- URLパラメータからカードIDとリアクションID

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| - | - | 削除成功時はコンテンツなし |

### 出力先

- Turbo Stream: 画面の動的更新（要素削除）
- JSON: API利用時のレスポンス（204 No Content）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ DELETE /cards/:card_id/reactions/:id へのリクエストを受信

2. 認証・認可確認
   └─ CardScopedモジュールのset_cardでカードアクセス権限を確認

3. リアクタブル・リアクション設定
   ├─ set_reactableでカードをリアクタブルとして設定
   └─ set_reactionでリアクションを取得

4. リアクター確認
   └─ ensure_permission_to_administer_reactionでリアクターと現在のユーザーが一致するか確認

5. リアクション削除
   └─ @reaction.destroy でリアクションを削除

6. レスポンス生成
   ├─ Turbo Stream: reactions/destroy.turbo_stream.erb をレンダリング
   └─ JSON: 204 No Content を返却
```

### フローチャート

```mermaid
flowchart TD
    A[DELETE /cards/:card_id/reactions/:id] --> B{認証済み?}
    B -->|No| C[401 Unauthorized]
    B -->|Yes| D[set_card: カード取得]
    D --> E{カードアクセス可能?}
    E -->|No| F[404 Not Found]
    E -->|Yes| G[set_reactable + set_reaction]
    G --> H{リアクション存在?}
    H -->|No| I[404 Not Found]
    H -->|Yes| J{リアクター確認}
    J -->|No| K[403 Forbidden]
    J -->|Yes| L[@reaction.destroyでリアクション削除]
    L --> M{リクエスト形式}
    M -->|Turbo Stream| N[reactions/destroy.turbo_stream.erb]
    M -->|JSON| O[204 No Content]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-50-01 | リアクター制限 | リアクション付与者のみが削除可能 | 常時 |
| BR-50-02 | カードスコープ | カードに紐づくリアクションのみ削除可能 | 常時 |
| BR-50-03 | 物理削除 | リアクションは物理削除され、復元不可 | 削除時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カード取得 | cards | SELECT | 対象カードの存在確認とアクセス権限確認 |
| リアクション取得 | reactions | SELECT | 対象リアクションの存在確認 |
| リアクション削除 | reactions | DELETE | リアクションレコードを物理削除 |

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

#### reactions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | WHERE reactable_type = 'Card' AND reactable_id = @card.id AND id = params[:id] | リアクション取得 |
| DELETE | * | WHERE id = @reaction.id | 物理削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | 認証エラー | 未認証状態でアクセス | ログイン画面へリダイレクト |
| 403 | Forbidden | リアクター以外が削除しようとした | エラーメッセージ表示 |
| 404 | Not Found | カードまたはリアクションが存在しない/アクセス権限なし | エラーメッセージ表示 |

### リトライ仕様

特になし

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

- `destroy` メソッドにより、削除処理はトランザクション内で実行される
- 失敗時は自動的にロールバックされる

## パフォーマンス要件

- リアクション削除のレスポンス時間: 300ms以内

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

- 認証必須: ログイン済みユーザーのみアクセス可能
- 認可:
  - CardScopedモジュールにより、アクセス可能なカードのリアクションのみ操作可能
  - ensure_permission_to_administer_reactionにより、リアクション付与者のみ削除可能

## 備考

- リアクション削除のUIは、リアクションをクリック後に表示される削除ボタンから操作
- JavaScriptコントローラー（reaction-delete）で削除操作のUIを制御

---

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

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

### 推奨読解順序

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

まず、リアクションのデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | reaction.rb | `app/models/reaction.rb` | Reactionモデルの定義、reacterとの関連 |
| 1-2 | schema.rb | `db/schema.rb` | reactionsテーブルの構造（431-442行目） |

**読解のコツ**:
- `belongs_to :reacter, class_name: "User"` でリアクター（リアクション付与者）を参照
- `belongs_to :reactable, polymorphic: true` でカードまたはコメントに紐づく

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

処理の起点となるコントローラーを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | reactions_controller.rb | `app/controllers/cards/reactions_controller.rb` | destroyアクションの実装（28-35行目） |
| 2-2 | card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | カードスコープの共通処理 |

**主要処理フロー**:
1. **6-9行目**: `with_options only: :destroy` で削除時のみ`set_reaction`と`ensure_permission_to_administer_reaction`を実行
2. **28-35行目**: `destroy` アクションでリアクション削除
3. **29行目**: `@reaction.destroy` で削除実行
4. **41-43行目**: `set_reaction` でリアクション取得
5. **45-47行目**: `ensure_permission_to_administer_reaction` でリアクター確認

#### Step 3: ビューレンダリングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | destroy.turbo_stream.erb | `app/views/reactions/destroy.turbo_stream.erb` | Turbo Streamレスポンス |
| 3-2 | _reaction.html.erb | `app/views/reactions/_reaction.html.erb` | リアクション表示と削除ボタン |

**主要処理フロー**:
- **destroy.turbo_stream.erb 1行目**: `turbo_stream.remove @reaction` でDOM要素を削除
- **_reaction.html.erb 18-24行目**: 削除ボタン（`button_to`）の実装
- **_reaction.html.erb 1-9行目**: `data-controller="reaction-delete"` でJSコントローラーを設定

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

```
DELETE /cards/:card_id/reactions/:id
    │
    ├─ CardScoped#set_card
    │      └─ Current.user.accessible_cards.find_by!(number: params[:card_id])
    │
    ├─ Cards::ReactionsController#set_reactable
    │      └─ @reactable = @card
    │
    ├─ Cards::ReactionsController#set_reaction
    │      └─ @reaction = @reactable.reactions.find(params[:id])
    │
    ├─ Cards::ReactionsController#ensure_permission_to_administer_reaction
    │      └─ head :forbidden if Current.user != @reaction.reacter
    │
    └─ Cards::ReactionsController#destroy
           │
           ├─ @reaction.destroy
           │      └─ Reaction.destroy (物理削除)
           │
           └─ respond_to
                  ├─ format.turbo_stream
                  │      └─ reactions/destroy.turbo_stream.erb
                  │             └─ turbo_stream.remove @reaction
                  │
                  └─ format.json
                         └─ head :no_content (204)
```

### データフロー図

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

DELETE リクエスト ─────▶ Cards::ReactionsController#destroy
  ├─ card_id                  │
  └─ id (reaction_id)         ├─▶ set_reactable + set_reaction
                              │      └─▶ @reactable, @reaction
                              │
                              ├─▶ ensure_permission_to_administer_reaction
                              │      └─▶ Current.user == @reaction.reacter?
                              │
                              └─▶ @reaction.destroy
                                     │
                                     ▼
                                  DB削除
                                  (reactions DELETE)
                                     │
                                     ▼
                          ┌──────────┴──────────┐
                          │                     │
                   Turbo Stream              JSON
                   (turbo_stream.remove)   (204 No Content)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| reactions_controller.rb | `app/controllers/cards/reactions_controller.rb` | コントローラー | リアクションCRUD処理 |
| card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | Concern | カードスコープ共通処理 |
| reaction.rb | `app/models/reaction.rb` | モデル | リアクションのデータモデル定義 |
| destroy.turbo_stream.erb | `app/views/reactions/destroy.turbo_stream.erb` | ビュー | Turbo Streamレスポンス（DOM削除） |
| _reaction.html.erb | `app/views/reactions/_reaction.html.erb` | ビュー | リアクション表示と削除ボタン |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
| schema.rb | `db/schema.rb` | スキーマ | reactionsテーブル定義 |
