# 機能設計書 20-カード編集

## 概要

本ドキュメントは、カードのタイトル、説明、画像を編集する機能の設計を記載する。

### 本機能の処理概要

本機能は、既存のカードの内容を編集・更新する機能である。インライン編集（edit.html.erb）またはTurbo Streamsによるリアルタイム保存に対応し、タイトル、説明（リッチテキスト）、添付画像を変更できる。

**業務上の目的・背景**：タスクの詳細情報は、作業の進行に伴って更新される必要がある。本機能により、ユーザーはカードの内容をリアルタイムに編集でき、自動保存機能（local-save）により編集内容が一時的に保持される。

**機能の利用シーン**：カード詳細画面またはカード編集画面から、ユーザーがタイトルや説明を編集する。保存はフォーム送信またはショートカットキー（Ctrl/Cmd+Enter）で実行される。

**主要な処理内容**：
1. カードのタイトル、説明、画像を受け取る
2. カードレコードを更新
3. Turbo Streamsで関連UIを更新
4. 編集モードから表示モードに遷移

**関連システム・外部連携**：Turbo Streamsによるリアルタイム更新。Action Text（Lexxy）によるリッチテキスト編集。Active Storageによる画像管理。

**権限による制御**：ユーザーがアクセス可能なカード（accessible_cards）のみ編集可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | カード編集画面 | 主画面 | カードのタイトル・説明・画像を編集 |
| 19 | カード詳細画面 | 参照画面 | インライン編集の元画面 |

## 機能種別

CRUD操作（Update）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | BigInt/String | Yes | カード番号（number） | 存在するカードであること、ユーザーがアクセス可能であること |
| card[title] | String | No | カードタイトル | 最大255文字 |
| card[description] | String | No | カード説明（リッチテキスト） | - |
| card[image] | File | No | カード画像 | - |
| card[created_at] | DateTime | No | 作成日時（インポート用） | - |
| card[last_active_at] | DateTime | No | 最終アクティブ日時（インポート用） | - |

### 入力データソース

- URLパラメータ: id（カード番号）
- リクエストボディ: card[title]、card[description]、card[image]
- セッション: 認証済みユーザー情報（Current.user）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| card.id | UUID | 更新されたカードのID |
| card.title | String | 更新後のタイトル |
| card.description | ActionText::RichText | 更新後の説明 |
| card.image | ActiveStorage::Attachment | 更新後の画像 |
| card.updated_at | DateTime | 更新日時 |

### 出力先

- データベース: cardsテーブル、action_text_rich_textsテーブル、active_storage_*テーブル
- 画面表示: Turbo Streamsによる動的更新
- JSONレスポンス: カード情報（API経由の場合）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信（PATCH/PUT /cards/:id）
   └─ set_cardによりnumberからカードを取得

2. パラメータ取得
   └─ card_params: title, description, image, created_at, last_active_at

3. カード更新
   └─ @card.update!(card_params)

4. レスポンス生成
   └─ Turbo Streams: 複数要素をmorph更新
   └─ JSON: カード情報を返す
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[set_card: カード取得]
    B --> C[card_paramsを抽出]
    C --> D[@card.update!]
    D --> E{バリデーション}
    E -->|失敗| F[422 Unprocessable Entity]
    E -->|成功| G[UPDATE実行]
    G --> H{レスポンス形式}
    H -->|Turbo Streams| I[morph更新]
    H -->|JSON| J[render :show]
    I --> K[card_containerを更新]
    K --> L[editフレームを更新]
    L --> M[closure_toggleを更新]
    M --> N[終了]
    J --> N
    F --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 自動タイムスタンプ | 更新時にupdated_atが自動更新される | 常時 |
| BR-02 | ボードタッチ | 公開カード更新時に親ボードのupdated_atが更新される | published状態の場合 |
| BR-03 | インライン編集 | 編集モードから表示モードへのシームレスな遷移 | Turbo Streams使用時 |

### 計算ロジック

該当なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カード更新 | cards | UPDATE | title, updated_at等を更新 |
| 説明更新 | action_text_rich_texts | INSERT/UPDATE | リッチテキストコンテンツ |
| 画像更新 | active_storage_attachments | INSERT/DELETE | 画像添付の変更 |
| ボード更新 | boards | UPDATE | 親ボードのupdated_at |

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

#### cards

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | title | card_params[:title] | 指定時のみ更新 |
| UPDATE | updated_at | 現在時刻 | 自動更新 |

#### action_text_rich_texts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | body | card_params[:description] | has_rich_text経由 |
| INSERT/UPDATE | record_id | @card.id | カードID |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 存在しないnumberが指定 | RecordNotFound例外 |
| 422 | Unprocessable Entity | バリデーションエラー | エラーメッセージ表示 |
| 401 | Unauthorized | 未認証ユーザー | ログイン画面へリダイレクト |

### リトライ仕様

該当なし（失敗時はエラーを返す）

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

- update!メソッドによる暗黙のトランザクション
- Action Text/Active Storageの更新も同一トランザクション

## パフォーマンス要件

- レスポンス時間: 300ms以内
- 画像アップロード時は追加時間が必要

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

- 認証: ログイン済みユーザーのみアクセス可能
- 認可: accessible_cardsによりアクセス可能なカードのみ
- パラメータ: Strong Parameters（card_params）で許可パラメータを制限
- XSS対策: Action Textによるサニタイズ

## 備考

- local-saveコントローラーにより編集内容がローカルストレージに一時保存される
- 下書きカードと公開カードで異なるコンテナパーシャルを使用

---

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

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

### 推奨読解順序

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

カードの編集対象フィールドを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | card.rb | `app/models/card.rb` | has_rich_text :description、has_one_attached :image |

**読解のコツ**: has_rich_textはAction Textによるリッチテキスト機能。別テーブル（action_text_rich_texts）に保存される。

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

リクエストを受け付けるコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | cards_controller.rb | `app/controllers/cards_controller.rb` | updateアクションの実装 |

**主要処理フロー**:
1. **5行目**: before_action :set_cardでカードを取得
2. **33-40行目**: updateアクションで@card.update!を実行
3. **36行目**: Turbo Streams形式のレスポンス
4. **37行目**: JSON形式ではrender :showを返す

#### Step 3: 編集フォームを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | edit.html.erb | `app/views/cards/edit.html.erb` | インライン編集フォームの構造 |

**主要処理フロー**:
- **1行目**: turbo_frame_tagで編集フレームをラップ
- **9-27行目**: form_withでフォームを構築
- **13-16行目**: タイトル入力（autoresize対応）
- **19-23行目**: 説明入力（rich_textarea、Lexxy対応）

#### Step 4: 更新後のUI反映を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | update.turbo_stream.erb | `app/views/cards/update.turbo_stream.erb` | Turbo Streamsによる複数要素更新 |

**主要処理フロー**:
- **1-2行目**: card_containerをmorph更新（下書き/公開で異なるパーシャル）
- **4-6行目**: editフレームを表示モードに更新
- **8-10行目**: closure_toggleを更新

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

```
CardsController#update
    │
    ├─ set_card
    │      └─ Current.user.accessible_cards.find_by!(number: params[:id])
    │
    ├─ card_params
    │      └─ params.expect(card: [:title, :description, :image, ...])
    │
    └─ @card.update!(card_params)
           │
           ├─ ActiveRecord UPDATE cards
           │
           ├─ Action Text (description)
           │      └─ action_text_rich_texts UPDATE
           │
           └─ Active Storage (image)
                  └─ active_storage_attachments INSERT/DELETE
```

### データフロー図

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

PATCH /cards/:number
  └─ card[title]
  └─ card[description]
  └─ card[image]
         │
         ▼
┌─────────────────┐
│ card取得        │ ───▶ accessible_cards.find_by!
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ update!        │ ───▶ cards, action_text_rich_texts, active_storage_*
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ Turbo Streams  │ ───▶ morph更新（3箇所）
└─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cards_controller.rb | `app/controllers/cards_controller.rb` | コントローラー | カード更新リクエストの処理 |
| card.rb | `app/models/card.rb` | モデル | カードモデル |
| edit.html.erb | `app/views/cards/edit.html.erb` | ビュー | インライン編集フォーム |
| update.turbo_stream.erb | `app/views/cards/update.turbo_stream.erb` | ビュー | 更新後のUI反映 |
| _container.html.erb | `app/views/cards/_container.html.erb` | パーシャル | カードコンテナ |
| schema.rb | `db/schema.rb` | スキーマ | cardsテーブル定義（202-217行目） |
