# 機能設計書 51-コメントにリアクション

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるコメントへのリアクション追加機能の詳細設計を記述する。

### 本機能の処理概要

コメントに対して絵文字リアクションを追加することで、ユーザー間のコミュニケーションを円滑にし、テキストベースのやり取りを補完する機能である。

**業務上の目的・背景**：コメントへのリアクションは、チームメンバー間の非言語的なコミュニケーションを可能にする。「いいね」や「了解」といった簡潔な意思表示をリアクションで行うことで、新たなコメントを追加せずにフィードバックを伝えることができ、コメント欄の視認性を保ちながらチームの意思疎通を促進する。

**機能の利用シーン**：カード詳細画面においてコメントを閲覧中に、特定のコメントに対して賛同・了解・感謝などの意思を簡単に表現したい場合に使用される。また、コメントへの反応を素早く示すことで、議論の活性化やチームの一体感醸成にも貢献する。

**主要な処理内容**：
1. ユーザーがコメントのリアクションボタンをクリック
2. 絵文字選択画面が表示され、ユーザーが絵文字を選択
3. 選択された絵文字でReactionレコードがデータベースに作成される
4. カードのlast_active_atが更新され、アクティビティとして記録される
5. Turbo Streamで画面がリアルタイム更新される

**関連システム・外部連携**：Action Cable経由でリアルタイム更新が行われ、他のユーザーの画面にもリアクションが反映される。

**権限による制御**：ログインユーザーのみがリアクションを追加できる。対象のカードにアクセス権限を持つユーザーに限定される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 30 | カードコメント詳細画面 | 主画面 | コメントに絵文字リアクションを追加 |

## 機能種別

CRUD操作（CREATE）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| card_id | String(Number) | Yes | 対象カードの番号 | カード番号として存在すること |
| comment_id | UUID | Yes | 対象コメントのID | コメントIDとして存在すること |
| reaction[content] | String | Yes | リアクションの内容（絵文字） | 最大16文字 |

### 入力データソース

画面入力（リアクション選択画面）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| reaction | Reaction | 作成されたリアクションオブジェクト |

### 出力先

- 画面表示（Turbo Stream）
- JSONレスポンス（API利用時）
- データベース（reactionsテーブル）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POST /cards/:card_id/comments/:comment_id/reactions
2. 認証・認可チェック
   └─ CardScopedコンサーンによるカードアクセス権確認
3. コメント取得
   └─ 指定されたcomment_idでコメントを検索
4. リアクション作成
   └─ @reactable.reactions.create!でリアクションを作成
5. カードアクティビティ更新
   └─ card.touch_last_active_atでカードの最終アクティブ日時を更新
6. レスポンス返却
   └─ Turbo Stream/JSONでレスポンスを返す
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[カードアクセス権確認]
    B --> C{アクセス権あり?}
    C -->|No| D[403 Forbidden]
    C -->|Yes| E[コメント取得]
    E --> F{コメント存在?}
    F -->|No| G[404 Not Found]
    F -->|Yes| H[リアクション作成]
    H --> I[カードアクティビティ更新]
    I --> J{リクエスト形式}
    J -->|Turbo Stream| K[Turbo Stream返却]
    J -->|JSON| L[201 Created返却]
    K --> M[終了]
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-51-01 | リアクション追加権限 | ログインユーザーかつカードへのアクセス権を持つユーザーのみリアクション可能 | 常時 |
| BR-51-02 | リアクター自動設定 | リアクションのreacterは現在のログインユーザーが自動的に設定される | リアクション作成時 |
| BR-51-03 | アカウント自動設定 | リアクションのaccountはreactableのaccountが自動的に設定される | リアクション作成時 |

### 計算ロジック

該当なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| リアクション作成 | reactions | INSERT | 新しいリアクションレコードを作成 |
| アクティビティ更新 | cards | UPDATE | last_active_atを現在時刻に更新 |
| コメント更新 | comments | UPDATE | updated_atを更新（touch） |

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

#### reactions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成UUID | 主キー |
| INSERT | account_id | reactable.account_id | デフォルト値で自動設定 |
| INSERT | reactable_id | コメントのID | ポリモーフィック関連 |
| INSERT | reactable_type | "Comment" | ポリモーフィック関連 |
| INSERT | reacter_id | Current.user.id | デフォルト値で自動設定 |
| INSERT | content | パラメータから取得 | 絵文字（最大16文字） |
| INSERT | created_at | 現在時刻 | - |
| INSERT | updated_at | 現在時刻 | - |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | カードへのアクセス権がない | エラーページ表示 |
| 404 | Not Found | カードまたはコメントが存在しない | エラーページ表示 |
| 422 | Unprocessable Entity | バリデーションエラー | エラーメッセージ表示 |

### リトライ仕様

リトライは不要（即時エラー返却）

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

リアクション作成はRailsのデフォルトトランザクション内で実行される。after_createコールバックでカードのtouch_last_active_atが呼ばれ、同一トランザクション内で完了する。

## パフォーマンス要件

- レスポンス時間: 200ms以内
- 同時リアクション数の制限なし

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

- ログイン必須（ApplicationControllerで制御）
- CardScopedコンサーンによるカードアクセス権チェック
- CSRFトークン検証（Railsデフォルト）
- リアクション内容は16文字以内に制限

## 備考

- リアクションの絵文字は複数文字の絵文字（結合文字を含む）も対応
- Turbo Streamにより、リアルタイムで画面が更新される

---

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

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

### 推奨読解順序

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

まず、リアクションのデータモデルとポリモーフィック関連を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | reaction.rb | `app/models/reaction.rb` | Reactionモデルの構造、belongs_to関連、コールバック |

**読解のコツ**:
- `belongs_to :reactable, polymorphic: true`により、CardとCommentの両方にリアクションを付けられる設計
- `default: -> { reactable.account }`でアカウントが自動設定される
- `after_create :register_card_activity`でカードのアクティビティが自動更新される

**主要処理フロー**:
- **1-4行目**: モデル定義とbelongs_to関連
- **6行目**: orderedスコープ（作成日時順）
- **8行目**: カードアクティビティ登録のコールバック
- **12-15行目**: register_card_activityメソッド（カードのlast_active_at更新）

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

リクエストを受け取るコントローラーの処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | reactions_controller.rb | `app/controllers/cards/comments/reactions_controller.rb` | createアクションの処理フロー |

**主要処理フロー**:
- **1-2行目**: コントローラー定義、CardScopedのinclude
- **4-5行目**: before_actionでコメントとreactableを設定
- **20-26行目**: createアクション（リアクション作成とレスポンス返却）
- **39-40行目**: set_commentメソッド（コメントの取得）
- **43-44行目**: set_reactableメソッド（@commentを@reactableに設定）

**読解のコツ**:
- CardScopedコンサーンによりカードのアクセス権チェックとカード取得が行われる
- `params.expect(reaction: :content)`でStrong Parametersを使用

#### Step 3: CardScopedコンサーンを理解する

カードへのアクセス権制御の仕組みを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | カードのスコープ設定とアクセス権チェック |

**主要処理フロー**:
- **5行目**: before_actionでset_cardとset_boardを実行
- **9-11行目**: set_cardメソッド（accessible_cardsからカード取得）
- **13-15行目**: set_boardメソッド（カードからボードを取得）

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

```
Cards::Comments::ReactionsController#create
    │
    ├─ [before_action] CardScoped#set_card
    │      └─ Current.user.accessible_cards.find_by!(number: params[:card_id])
    │
    ├─ [before_action] CardScoped#set_board
    │      └─ @board = @card.board
    │
    ├─ [before_action] #set_comment
    │      └─ @card.comments.find(params[:comment_id])
    │
    ├─ [before_action] #set_reactable
    │      └─ @reactable = @comment
    │
    └─ @reactable.reactions.create!(params.expect(reaction: :content))
           │
           ├─ [default] account = reactable.account
           ├─ [default] reacter = Current.user
           └─ [after_create] Reaction#register_card_activity
                  └─ reactable.card.touch_last_active_at
```

### データフロー図

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

card_id     ───▶ CardScoped#set_card    ───▶ @card
comment_id  ───▶ #set_comment           ───▶ @comment
                       │
                       ▼
                 #set_reactable         ───▶ @reactable = @comment
                       │
                       ▼
reaction    ───▶ reactions.create!      ───▶ Reaction (DB)
[content]                │
                         ▼
                 register_card_activity ───▶ Card.last_active_at (UPDATE)
                         │
                         ▼
                 Turbo Stream / JSON    ───▶ ブラウザ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| reactions_controller.rb | `app/controllers/cards/comments/reactions_controller.rb` | コントローラー | コメントリアクションのCRUD処理 |
| reaction.rb | `app/models/reaction.rb` | モデル | リアクションのデータモデル |
| card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | コンサーン | カードスコープの共通処理 |
| create.turbo_stream.erb | `app/views/reactions/create.turbo_stream.erb` | ビュー | Turbo Streamレスポンス |
| index.html.erb | `app/views/reactions/index.html.erb` | ビュー | リアクション一覧表示 |
| new.html.erb | `app/views/reactions/new.html.erb` | ビュー | リアクション選択画面 |
| _reaction.html.erb | `app/views/reactions/_reaction.html.erb` | パーシャル | 個別リアクション表示 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
| schema.rb | `db/schema.rb` | スキーマ | データベーススキーマ定義 |
