# 機能設計書 15-カラム削除

## 概要

本ドキュメントは、カラムを削除する機能の設計を記載する。

### 本機能の処理概要

本機能は、ボードからカラム（列）を削除する機能である。カラムを削除すると、そのカラムに属していたカードはカラム未割り当て（トリアージ待ち）状態になる。

**業務上の目的・背景**：プロジェクトのワークフロー変更に伴い、不要になったカラムを削除する必要がある。例えば、「レビュー中」のステップが不要になった場合や、カラムを統合したい場合に使用される。本機能により、ボードの構成を柔軟に変更できる。

**機能の利用シーン**：ボードカラム詳細画面から、ユーザーがカラムの削除操作を実行する。削除前に確認ダイアログが表示され、確認後に削除が実行される。

**主要な処理内容**：
1. 対象カラムを特定
2. カラムを削除（関連カードのcolumn_idはnullに設定）
3. 削除後に関連カード全体のtouch_allを実行
4. 削除後にリダイレクトまたはJSONレスポンス

**関連システム・外部連携**：削除後のリダイレクト先はボード詳細画面。JSONレスポンスにも対応し、API経由での削除も可能。

**権限による制御**：ユーザーがアクセス可能なボードに属するカラムのみ削除可能。BoardScopedによりボードへのアクセス権を検証。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 13 | ボードカラム詳細画面 | 主画面 | カラムを削除する操作 |
| 10 | ボード詳細画面 | リダイレクト先 | 削除後のリダイレクト先 |

## 機能種別

CRUD操作（Delete）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| board_id | UUID | Yes | 対象ボードのID | 存在するボードであること、ユーザーがアクセス可能であること |
| id | UUID | Yes | 対象カラムのID | 該当ボードに属するカラムであること |

### 入力データソース

- URLパラメータ: board_id、id（カラムID）
- セッション: 認証済みユーザー情報（Current.user）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| なし | - | 削除処理のため出力データなし |

### 出力先

- データベース: columnsテーブル（DELETE）、cardsテーブル（UPDATE）
- 画面表示: ボード詳細画面へリダイレクト
- JSONレスポンス: 204 No Content（API経由の場合）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信（DELETE /boards/:board_id/columns/:id）
   └─ BoardScopedによりboard_idからボードを取得
   └─ set_columnによりidからカラムを取得

2. カラム削除
   └─ @column.destroy

3. after_destroy_commit: 関連カードのtouch_all
   └─ board.cards.touch_all（カラムに関わらず全カード）

4. レスポンス生成
   └─ HTML: ボード詳細画面へリダイレクト
   └─ JSON: 204 No Content
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[BoardScoped: ボード取得]
    B --> C[set_column: カラム取得]
    C --> D[@column.destroy]
    D --> E{削除成功?}
    E -->|成功| F[dependent: :nullify]
    F --> G[cards.column_id = NULL]
    G --> H[after_destroy_commit]
    H --> I[board.cards.touch_all]
    I --> J{レスポンス形式}
    J -->|HTML| K[redirect_back_or_to @board]
    J -->|JSON| L[204 No Content]
    K --> M[終了]
    L --> M
    E -->|失敗| M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | カード関連解除 | カラム削除時、関連カードのcolumn_idはnullに設定（dependent: :nullify） | カラム削除時 |
| BR-02 | カード一括更新 | カラム削除後、ボード内の全カードのupdated_atが更新される | カラム削除時 |
| BR-03 | カード保持 | カラム削除してもカード自体は削除されない | 常時 |

### 計算ロジック

該当なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カラム削除 | columns | DELETE | カラムレコード削除 |
| カード更新 | cards | UPDATE | column_idをNULLに設定 |
| カード更新 | cards | UPDATE | ボード内全カードのupdated_atを更新 |

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

#### columns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | * | id = params[:id] | レコード削除 |

#### cards

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | column_id | NULL | dependent: :nullifyによる自動更新 |
| UPDATE | updated_at | 現在時刻 | touch_allで一括更新（全カード） |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | 存在しないboard_id/column_idが指定 | RecordNotFound例外 |
| 401 | Unauthorized | 未認証ユーザー | ログイン画面へリダイレクト |

### リトライ仕様

該当なし（削除操作は冪等）

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

- destroyメソッドによる暗黙のトランザクション
- dependent: :nullifyはdestroyと同一トランザクション内で実行
- after_destroy_commitはトランザクションコミット後に実行

## パフォーマンス要件

- レスポンス時間: 300ms以内
- touch_all: ボード内全カードが対象のため、大量カード時は影響大

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

- 認証: ログイン済みユーザーのみアクセス可能
- 認可: BoardScopedによりCurrent.user.boardsでアクセス可能なボードのみ
- 削除確認: UIレベルで確認ダイアログを表示（コントローラーでは制御しない）

## 備考

- カラム削除後もカードは残り、トリアージ待ち状態として表示される
- 削除されたカラムは復元不可

---

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

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

### 推奨読解順序

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

カラム削除時の連動処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | column.rb | `app/models/column.rb` | has_many :cards, dependent: :nullifyとafter_destroy_commit |

**読解のコツ**: dependent: :nullifyはカラム削除時に関連カードのcolumn_idをNULLに設定する。after_destroy_commitはコミット後に全カードをtouch_allする。

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

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

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

**主要処理フロー**:
1. **4行目**: before_action :set_columnでカラムを取得
2. **34-41行目**: destroyアクションで@column.destroyを実行
3. **38行目**: HTML形式ではredirect_back_or_to @boardでリダイレクト
4. **39行目**: JSON形式では204 No Contentを返す

#### Step 3: コールバック処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | column.rb | `app/models/column.rb` | after_destroy_commitコールバック（9行目） |

**主要処理フロー**:
- **6行目**: has_many :cards, dependent: :nullifyで関連カードの外部キーをNULLに
- **9行目**: after_destroy_commitでboard.cards.touch_allを実行

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

```
Boards::ColumnsController#destroy
    │
    ├─ BoardScoped (concern)
    │      └─ set_board (Current.user.boards.find)
    │
    ├─ set_column
    │      └─ @board.columns.find(params[:id])
    │
    └─ @column.destroy
           │
           ├─ dependent: :nullify
           │      └─ UPDATE cards SET column_id = NULL
           │
           └─ after_destroy_commit
                  └─ board.cards.touch_all
```

### データフロー図

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

DELETE /boards/:board_id/columns/:id
         │
         ▼
┌─────────────────┐
│ board/column取得 │ ───▶ Current.user.boards.find
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ @column.destroy │ ───▶ DELETE columns
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ nullify cards  │ ───▶ UPDATE cards SET column_id = NULL
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ touch_all      │ ───▶ UPDATE cards SET updated_at
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ redirect/204   │ ───▶ ボード詳細画面 / No Content
└─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| columns_controller.rb | `app/controllers/boards/columns_controller.rb` | コントローラー | カラム削除リクエストの処理 |
| board_scoped.rb | `app/controllers/concerns/board_scoped.rb` | concern | ボード取得 |
| column.rb | `app/models/column.rb` | モデル | カラムモデル、dependent: :nullify、after_destroy_commit |
| schema.rb | `db/schema.rb` | スキーマ | columnsテーブル定義（239-249行目） |
