# 機能設計書 22-カード公開

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるカード公開機能の設計を記載するものです。

### 本機能の処理概要

カード公開機能は、下書き状態（drafted）で作成されたカードを公開状態（published）に変更する機能です。公開されたカードはボード上で他のユーザーにも表示されるようになります。

**業務上の目的・背景**：カードを作成する際、すぐに公開せずに内容を下書きとして準備してから公開したいというニーズがあります。この機能により、ユーザーは内容を十分に精査した上でカードをチームに共有できます。また、「もう1つ追加」オプションにより、連続してカードを作成する効率的なワークフローを実現します。

**機能の利用シーン**：
- 新規カードの内容（タイトル、説明、画像）を入力した後、チームに公開する場合
- 複数のカードを連続して作成したい場合（Add Anotherオプション）
- 下書きの内容を確認後、正式にタスクとして登録する場合

**主要な処理内容**：
1. 下書きカードのステータスを「published」に変更
2. カード作成日時（created_at）を公開時点に更新
3. 公開イベントの記録
4. オプションで新しい下書きカードを自動作成

**関連システム・外部連携**：Webhookが設定されている場合、カード公開時にイベントが通知されます。

**権限による制御**：カードを作成したユーザーまたはボードにアクセス可能なユーザーが公開操作を実行できます。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 21 | カード下書き画面 | 主画面 | 下書き編集と公開ボタンの表示 |
| 10 | ボード詳細画面 | 結果表示画面 | 公開後のリダイレクト先 |

## 機能種別

CRUD操作（UPDATE） / ステータス変更

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| card_id | String | Yes | カード番号（number） | 存在する下書きカードであること |
| creation_type | String | No | "add_another"の場合、新しい下書きを作成 | 特になし |

### 入力データソース

- URL パラメータからカード番号を取得
- リクエストパラメータから creation_type を取得

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| redirect_to | URL | 公開後のリダイレクト先 |

### 出力先

- 通常公開：ボード詳細画面へリダイレクト
- Add Another：新しいカードの下書き画面へリダイレクト

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信（POST /cards/:card_id/publish）
   └─ カード番号をURLパラメータから取得

2. カード取得
   └─ CardScoped concernによりカードを取得

3. カード公開処理
   └─ @card.publish メソッドを呼び出し
       ├─ created_at を現在時刻に更新
       ├─ ステータスを "published" に変更
       └─ 公開イベントを記録

4. リダイレクト処理
   └─ creation_type が "add_another" の場合
       ├─ Yes: 新しい下書きカードを作成し、その下書き画面へリダイレクト
       └─ No: ボード詳細画面へリダイレクト
```

### フローチャート

```mermaid
flowchart TD
    A[公開リクエスト受信] --> B[カード取得]
    B --> C[@card.publish 実行]
    C --> D[created_at 更新]
    D --> E[status を published に変更]
    E --> F[イベント記録]
    F --> G{creation_type?}
    G -->|add_another| H[新規下書きカード作成]
    H --> I[下書き画面へリダイレクト]
    G -->|その他| J[ボード画面へリダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-22-01 | 公開日時設定 | カードの created_at は公開時点の日時に設定される | すべての公開操作 |
| BR-22-02 | ステータス変更 | drafted から published への一方向の変更のみ可能 | すべての公開操作 |
| BR-22-03 | デフォルトタイトル | タイトルが空の場合、"Untitled" が自動設定される | 公開時にタイトルが未設定の場合 |
| BR-22-04 | イベント記録 | 公開時に :published イベントが記録される | すべての公開操作 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カード公開 | cards | UPDATE | ステータスと作成日時の更新 |
| イベント記録 | events | INSERT | 公開イベントの記録 |
| 新規下書き作成 | cards | INSERT | Add Another選択時のみ |

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

#### cards

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | status | "published" | ステータス変更 |
| UPDATE | created_at | Time.current | 公開時刻を設定 |
| UPDATE | title | "Untitled" | タイトルが空の場合のみ |

#### events

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | action | "published" | イベント種別 |
| INSERT | eventable_type | "Card" | 対象モデル |
| INSERT | eventable_id | カードのID | 対象カードのID |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | RecordNotFound | 指定されたカードが存在しない、またはアクセス権がない | 適切なエラー画面を表示 |
| 422 | UnprocessableEntity | すでに公開済みのカードに対して公開操作を行った場合 | エラーメッセージを表示 |

### リトライ仕様

特になし（冪等性：すでに公開済みの場合は再度公開しても状態は変わらない）

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

publish メソッド内で transaction ブロックを使用し、created_at の更新、ステータス変更、イベント記録を一括で処理します。いずれかが失敗した場合はロールバックされます。

## パフォーマンス要件

- 公開処理は500ms以内に完了すること

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

- 認証：ログインユーザーのみアクセス可能（ApplicationControllerで制御）
- 認可：カードにアクセス可能なユーザーのみ操作可能
- CSRF対策：Rails標準のCSRFトークン検証を適用

## 備考

- 公開後に再度下書き状態に戻すことはできない
- Add Anotherオプションは連続的なカード作成に便利

---

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

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

### 推奨読解順序

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

カードのステータス管理の仕組みを把握することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | statuses.rb | `app/models/card/statuses.rb` | Statusesモジュールでステータス管理とpublishメソッドを定義 |
| 1-2 | card.rb | `app/models/card.rb` | Cardモデルの全体構造。Statusesモジュールのinclude |

**読解のコツ**: Card::Statuses モジュールには `enum :status` でステータス定義があり、`publish` メソッドがメインの処理です。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | publishes_controller.rb | `app/controllers/cards/publishes_controller.rb` | createアクションが公開のエントリーポイント |
| 2-2 | card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | CardScopedでカード取得処理を共通化 |

**主要処理フロー**:
1. **4行目**: `@card.publish` - 公開処理の実行
2. **7-12行目**: リダイレクト先の分岐処理

#### Step 3: ステータス変更処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | statuses.rb | `app/models/card/statuses.rb` | publishメソッドの実装詳細 |

**主要処理フロー**:
- **5行目**: `enum :status` - drafted / published の定義
- **14-20行目**: `publish`メソッド
  - **16行目**: `self.created_at = Time.current` - 公開日時を設定
  - **17行目**: `published!` - ステータスを変更
  - **18行目**: `track_event :published` - イベントを記録

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

```
Cards::PublishesController#create
    │
    ├─ CardScoped#set_card (before_action)
    │      └─ Current.user.accessible_cards.find_by!(number: params[:card_id])
    │
    ├─ @card.publish
    │      └─ Card::Statuses#publish
    │             ├─ self.created_at = Time.current
    │             ├─ published!
    │             └─ track_event :published
    │
    └─ リダイレクト処理
           ├─ add_another? → 新規下書き作成 → card_draft_path(card)
           └─ else → @card.board
```

### データフロー図

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

POST /cards/:id/publish ──▶ PublishesController#create ──▶ redirect
  │                              │
  │ card_id                      ├─ @card.publish
  │ creation_type                │    ├─ created_at 更新
  │                              │    ├─ status 更新
  │                              │    └─ イベント記録
  │                              │
  │                              └─ creation_type チェック
  │                                   ├─ add_another → 新カード作成
  │                                   └─ else → ボードへ
  │
  └─ cards (UPDATE)
     events (INSERT)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| publishes_controller.rb | `app/controllers/cards/publishes_controller.rb` | コントローラー | 公開リクエストの受付と処理 |
| card_scoped.rb | `app/controllers/concerns/card_scoped.rb` | コントローラー(concern) | カード取得処理の共通化 |
| statuses.rb | `app/models/card/statuses.rb` | モデル(concern) | ステータス管理とpublishメソッド |
| card.rb | `app/models/card.rb` | モデル | カードモデル定義 |
| show.html.erb | `app/views/cards/drafts/show.html.erb` | ビュー | 下書き画面（公開ボタンを含む） |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（86行目） |
