# 機能設計書 49-カードにリアクション

## 概要

本ドキュメントは、カードに絵文字リアクションを追加する機能の設計を定義するものである。

### 本機能の処理概要

**業務上の目的・背景**：カンバンボードでのコミュニケーションにおいて、テキストによるコメントだけでなく、絵文字による簡易的なリアクションも重要な役割を果たす。カードへのリアクション機能は、タスクに対する賛同、感謝、注目などの感情を絵文字で表現することで、コメントを書くほどでもない場合の軽いコミュニケーションを可能にする。これにより、チームの一体感とモチベーション向上に寄与する。

**機能の利用シーン**：
- タスクの完了に対して祝福を表現したいとき
- カードの内容に賛同・同意を示したいとき
- 感謝や応援の気持ちを簡単に伝えたいとき
- 注目度の高いカードを視覚的にハイライトしたいとき

**主要な処理内容**：
1. POSTリクエストでリアクション内容（絵文字等）を受け取る
2. 対象カードへのアクセス権限を確認
3. リアクションをデータベースに新規登録
4. カードの最終アクティブ日時を更新
5. Turbo StreamまたはJSONレスポンスで結果を返却

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

**権限による制御**：カードへのアクセス権限を持つユーザーのみがリアクション可能。CardScopedモジュールにより、`Current.user.accessible_cards`でアクセス可能なカードのみ操作対象となる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 19 | カード詳細画面 | 参照画面 | カードに絵文字リアクションを追加 |
| 60 | リアクション一覧画面 | 参照画面 | リアクションの一覧を表示 |
| 61 | リアクション追加画面 | 主機能 | リアクションを追加 |

## 機能種別

CRUD操作（Create）/ コミュニケーション機能

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| card_id | Integer | Yes | 対象カードのID（URLパラメータ） | 存在確認、アクセス権限確認 |
| reaction[content] | String | Yes | リアクション内容（絵文字等、最大16文字） | 必須 |

### 入力データソース

- URLパラメータからカードID
- フォームデータまたはJSONからリアクション内容

## 出力仕様

### 出力データ

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

### 出力先

- Turbo Stream: 画面の動的更新（リアクション一覧を置換）
- JSON: API利用時のレスポンス（201 Created）

## 処理フロー

### 処理シーケンス

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

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

3. リアクタブル設定
   └─ set_reactableでカードをリアクション対象として設定

4. リアクション作成
   └─ @reactable.reactions.create!(params) でリアクションを新規作成

5. カードアクティビティ更新
   └─ after_create :register_card_activityでカードのlast_active_atを更新

6. レスポンス生成
   ├─ Turbo Stream: reactions/create.turbo_stream.erb をレンダリング
   └─ JSON: 201 Created を返却
```

### フローチャート

```mermaid
flowchart TD
    A[POST /cards/:card_id/reactions] --> B{認証済み?}
    B -->|No| C[401 Unauthorized]
    B -->|Yes| D[set_card: カード取得]
    D --> E{カードアクセス可能?}
    E -->|No| F[404 Not Found]
    E -->|Yes| G[set_reactable: カードをリアクタブルに設定]
    G --> H[reactions.create!でリアクション作成]
    H --> I[register_card_activity]
    I --> J{リクエスト形式}
    J -->|Turbo Stream| K[reactions/create.turbo_stream.erb]
    J -->|JSON| L[201 Created]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-49-01 | カードスコープ | カードへのアクセス権限があればリアクション可能 | 常時 |
| BR-49-02 | リアクター自動設定 | リアクションのreacter_idはCurrent.userが自動設定される | リアクション作成時 |
| BR-49-03 | アカウント自動設定 | リアクションのaccount_idはカードのaccount_idが自動設定される | リアクション作成時 |
| BR-49-04 | カードアクティビティ更新 | リアクション作成時にカードのlast_active_atが更新される | リアクション作成後 |
| BR-49-05 | ポリモーフィック関連 | リアクションはカードだけでなくコメントにも付けられる | 常時 |
| BR-49-06 | 文字数制限 | リアクション内容は最大16文字 | リアクション作成時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カード取得 | cards | SELECT | 対象カードの存在確認とアクセス権限確認 |
| リアクション作成 | reactions | INSERT | 新しいリアクションレコードを登録 |
| カード更新 | cards | UPDATE | touch :last_active_atにより最終アクティブ日時を更新 |

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

#### reactions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | UUID自動生成 | 主キー |
| INSERT | account_id | reactable.account_id | 自動設定 |
| INSERT | reactable_type | 'Card' | ポリモーフィック |
| INSERT | reactable_id | @card.id | ポリモーフィック |
| INSERT | reacter_id | Current.user.id | 自動設定 |
| INSERT | content | params[:reaction][:content] | 最大16文字 |
| INSERT | created_at | 現在時刻 | 自動設定 |
| INSERT | updated_at | 現在時刻 | 自動設定 |

#### cards

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | last_active_at | 現在時刻 | touch_last_active_atにより |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | 認証エラー | 未認証状態でアクセス | ログイン画面へリダイレクト |
| 404 | Not Found | カードが存在しない/アクセス権限なし | エラーメッセージ表示 |
| 422 | Unprocessable Entity | バリデーションエラー | エラーメッセージ表示 |

### リトライ仕様

特になし

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

- `create!` メソッドにより、リアクション作成がトランザクション内で実行される
- `after_create` コールバックにより、カードのアクティビティ更新も同一トランザクション内で実行

## パフォーマンス要件

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

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

- 認証必須: ログイン済みユーザーのみアクセス可能
- 認可: CardScopedモジュールにより、アクセス可能なカードのみ操作可能
- Strong Parameters: `params.expect(reaction: :content)` でパラメータをホワイトリスト制御
- 文字数制限: content カラムは最大16文字に制限

## 備考

- リアクションフォームは絵文字入力に対応したtext_fieldを使用
- `all_emoji?` メソッドで絵文字のみの場合は表示サイズを大きくする
- リアクションはカードとコメントの両方に対応（ポリモーフィック）

---

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

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

### 推奨読解順序

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

まず、リアクションのデータ構造とポリモーフィック関連を理解することが重要。

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

**読解のコツ**:
- `belongs_to :reactable, polymorphic: true, touch: true` でカードまたはコメントに紐づく
- `belongs_to :reacter, class_name: "User", default: -> { Current.user }` でリアクターを自動設定
- `after_create :register_card_activity` でカードのlast_active_atを更新

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

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

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

**主要処理フロー**:
1. **4行目**: `before_action :set_reactable` でカードをリアクタブルとして設定
2. **19-26行目**: `create` アクションでリアクション作成
3. **20行目**: `@reactable.reactions.create!(params.expect(reaction: :content))` で作成
4. **37-39行目**: `set_reactable` でカードをリアクタブルに設定

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | new.html.erb | `app/views/reactions/new.html.erb` | リアクション入力フォーム |
| 3-2 | create.turbo_stream.erb | `app/views/reactions/create.turbo_stream.erb` | Turbo Streamレスポンス |
| 3-3 | _reactions.html.erb | `app/views/reactions/_reactions.html.erb` | リアクション一覧テンプレート |
| 3-4 | _reaction.html.erb | `app/views/reactions/_reaction.html.erb` | リアクション個別テンプレート |

**主要処理フロー**:
- **new.html.erb 2-26行目**: リアクションフォーム（絵文字入力対応）
- **create.turbo_stream.erb 1-3行目**: `turbo_stream.replace` でリアクション一覧を更新
- **_reaction.html.erb 14-16行目**: 絵文字表示、all_emoji?で表示サイズ調整

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

```
POST /cards/:card_id/reactions
    │
    ├─ CardScoped#set_card
    │      └─ Current.user.accessible_cards.find_by!(number: params[:card_id])
    │
    ├─ Cards::ReactionsController#set_reactable
    │      └─ @reactable = @card
    │
    └─ Cards::ReactionsController#create
           │
           ├─ @reactable.reactions.create!(params.expect(reaction: :content))
           │      └─ Reaction.create!
           │             ├─ belongs_to :reactable, polymorphic: true
           │             ├─ belongs_to :reacter (Current.user)
           │             └─ after_create :register_card_activity
           │                    └─ reactable.card.touch_last_active_at
           │
           └─ respond_to
                  ├─ format.turbo_stream
                  │      └─ reactions/create.turbo_stream.erb
                  │             └─ turbo_stream.replace
                  │
                  └─ format.json
                         └─ head :created (201)
```

### データフロー図

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

POST リクエスト ───────▶ Cards::ReactionsController#create
  ├─ card_id                   │
  └─ reaction[content]         ├─▶ set_reactable
                               │      └─▶ @reactable = @card
                               │
                               └─▶ @reactable.reactions.create!
                                      │
                                      ├─▶ DB保存 (reactions)
                                      │
                                      └─▶ after_create
                                             └─▶ register_card_activity
                                                    └─▶ touch_last_active_at (cards)
                                                           │
                                                           ▼
                                             ┌─────────────┴─────────────┐
                                             │                           │
                                      Turbo Stream                    JSON
                                      (turbo_stream.replace)      (201 Created)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| 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` | モデル | リアクションのデータモデル定義 |
| new.html.erb | `app/views/reactions/new.html.erb` | ビュー | リアクション入力フォーム |
| create.turbo_stream.erb | `app/views/reactions/create.turbo_stream.erb` | ビュー | Turbo Streamレスポンス |
| _reactions.html.erb | `app/views/reactions/_reactions.html.erb` | ビュー | リアクション一覧テンプレート |
| _reaction.html.erb | `app/views/reactions/_reaction.html.erb` | ビュー | リアクション個別テンプレート |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
| schema.rb | `db/schema.rb` | スキーマ | reactionsテーブル定義 |
