# 機能設計書 13-カラム作成

## 概要

本ドキュメントは、新しいカラムをボードに追加する機能の設計を記載する。

### 本機能の処理概要

本機能は、カンバンボードに新しいカラム（列）を追加する機能である。カラムには名前と色を設定でき、ボード内でカードを分類・整理するために使用される。

**業務上の目的・背景**：プロジェクトの進行に伴い、新しいステータスやフェーズが必要になることがある。例えば、「レビュー中」「テスト待ち」といった新しいカラムを追加することで、より細かいタスク管理が可能となる。本機能により、ユーザーはボードのワークフローを柔軟にカスタマイズできる。

**機能の利用シーン**：ボード詳細画面において、ユーザーが「カラム追加」ボタンをクリックし、カラム名と色を入力して新しいカラムを作成する。作成されたカラムはボードの最後尾（最大position + 1）に配置される。

**主要な処理内容**：
1. カラム名と色のパラメータを受け取る
2. 新しいColumnレコードをデータベースに作成
3. positionを自動で最大値+1に設定
4. Turbo StreamsでUIを動的に更新

**関連システム・外部連携**：Turbo Streamsを使用してリアルタイムUI更新を行う。JSONレスポンスにも対応し、API経由での作成も可能。

**権限による制御**：ユーザーがアクセス可能なボードに対してのみカラムを追加可能。BoardScopedによりボードへのアクセス権を検証。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | ボード詳細画面 | 主画面 | 新しいカラムをボードに追加（API連携） |
| 11 | ボード新規作成画面 | 参照画面 | ボード作成時にデフォルトカラムを設定 |

## 機能種別

CRUD操作（Create）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| board_id | UUID | Yes | 対象ボードのID | 存在するボードであること、ユーザーがアクセス可能であること |
| column[name] | String | Yes | カラム名 | 最大255文字 |
| column[color] | String | No | カラムの色 | Color::COLORSに含まれる値、未指定時はデフォルト色 |

### 入力データソース

- URLパラメータ: board_id
- リクエストボディ: column[name]、column[color]
- セッション: 認証済みユーザー情報（Current.user）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| column.id | UUID | 作成されたカラムのID |
| column.name | String | カラム名 |
| column.color | String | カラムの色 |
| column.position | Integer | 表示順序（自動採番） |
| column.board_id | UUID | 所属ボードのID |

### 出力先

- データベース: columnsテーブル
- 画面表示: Turbo Streamsによる動的更新
- HTTPヘッダー: Location（作成されたリソースのURL）
- JSONレスポンス: 201 Created（API経由の場合）

## 処理フロー

### 処理シーケンス

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

2. パラメータ取得
   └─ column_params: name, color

3. カラム作成
   └─ @board.columns.create!(column_params)
   └─ before_createでpositionを自動設定
   └─ before_validationで色のデフォルト値を設定

4. レスポンス生成
   └─ Turbo Streams: UIに新カラムを挿入
   └─ JSON: 201 Created + Locationヘッダー
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[BoardScoped: ボード取得]
    B --> C[column_paramsを抽出]
    C --> D[columns.create!]
    D --> E{バリデーション}
    E -->|失敗| F[422 Unprocessable Entity]
    E -->|成功| G[before_create: position設定]
    G --> H[before_validation: color設定]
    H --> I[INSERT実行]
    I --> J{レスポンス形式}
    J -->|Turbo Streams| K[UIに新カラム挿入]
    J -->|JSON| L[201 Created + Location]
    K --> M[終了]
    L --> M
    F --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | position自動採番 | 新カラムのpositionは既存最大値+1 | カラム作成時 |
| BR-02 | 色のデフォルト値 | 色が未指定の場合はColor::COLORS.first | カラム作成時 |
| BR-03 | ボード関連付け | カラムは必ず1つのボードに属する | 常時 |

### 計算ロジック

```ruby
# position計算
max_position = board.columns.maximum(:position) || 0
new_column.position = max_position + 1
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| カラム作成 | columns | INSERT | 新規カラムレコード作成 |
| position取得 | columns | SELECT | 最大positionを取得 |

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

#### columns

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | MAX(position) | board_id = @board.id | 新position計算用 |
| INSERT | id | UUID自動生成 | 主キー |
| INSERT | account_id | board.account_id | デフォルト値 |
| INSERT | board_id | 対象ボードのID | 外部キー |
| INSERT | name | column_params[:name] | 必須 |
| INSERT | color | column_params[:color] または DEFAULT_COLOR | デフォルト値あり |
| INSERT | position | max_position + 1 | 自動採番 |
| INSERT | created_at | 現在時刻 | 自動設定 |
| INSERT | updated_at | 現在時刻 | 自動設定 |

## エラー処理

### エラーケース一覧

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

### リトライ仕様

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

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

- create!メソッドによる単一INSERT文のため、暗黙のトランザクション
- position計算とINSERTは同一トランザクション内で実行されないため、同時実行時に同じpositionが設定される可能性あり（後続のソートで解決）

## パフォーマンス要件

- レスポンス時間: 200ms以内
- position計算: columnsテーブルにboard_id + positionの複合インデックスあり

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

- 認証: ログイン済みユーザーのみアクセス可能
- 認可: BoardScopedによりCurrent.user.boardsでアクセス可能なボードのみ
- パラメータ: Strong Parameters（column_params）で許可パラメータを制限

## 備考

- カラム削除時は関連カードのcolumn_idがnullに設定される（dependent: :nullify）
- カラムの名前や色を変更すると、関連カード全てにtouch_allが実行される

---

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

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

### 推奨読解順序

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

カラムのデータ構造と自動設定される値を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | column.rb | `app/models/column.rb` | カラムモデルの基本構造。Colored, Positionedをinclude |
| 1-2 | colored.rb | `app/models/column/colored.rb` | 色のデフォルト値設定（before_validation） |
| 1-3 | positioned.rb | `app/models/column/positioned.rb` | positionの自動採番（before_create） |

**読解のコツ**: before_validationとbefore_createの順序に注意。colorはバリデーション前、positionは作成直前に設定される。

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

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

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

**主要処理フロー**:
1. **16行目**: @board.columns.create!(column_params)でカラム作成
2. **18-22行目**: Turbo StreamsとJSONで異なるレスポンスを返す
3. **48-50行目**: column_paramsでname, colorのみ許可

#### Step 3: 色とposition管理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | colored.rb | `app/models/column/colored.rb` | DEFAULT_COLORの設定（4行目）とbefore_validation（7行目） |
| 3-2 | positioned.rb | `app/models/column/positioned.rb` | set_positionメソッド（39-42行目）によるposition自動設定 |

**主要処理フロー**:
- **7行目（colored.rb）**: self[:color] ||= DEFAULT_COLOR.valueでデフォルト色を設定
- **39-42行目（positioned.rb）**: max_position + 1を計算してpositionに設定

#### Step 4: ビュー更新を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create.turbo_stream.erb | `app/views/boards/columns/create.turbo_stream.erb` | Turbo Streamsでカラムを追加 |

**主要処理フロー**:
- **1行目**: turbo_stream.beforeで既存要素の前に新カラムを挿入

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

```
Boards::ColumnsController#create
    │
    ├─ BoardScoped (concern)
    │      └─ set_board (Current.user.boards.find)
    │
    ├─ column_params
    │      └─ params.expect(column: [:name, :color])
    │
    └─ @board.columns.create!
           │
           ├─ Column::Colored (concern)
           │      └─ before_validation: set default color
           │
           └─ Column::Positioned (concern)
                  └─ before_create: set_position
```

### データフロー図

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

POST /boards/:board_id/columns
  └─ column[name]
  └─ column[color]
         │
         ▼
┌─────────────────┐
│ board取得       │ ───▶ Current.user.boards.find
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ color設定       │ ───▶ DEFAULT_COLOR (if blank)
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ position計算    │ ───▶ MAX(position) + 1
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ INSERT         │ ───▶ columns テーブル
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ Turbo Streams  │ ───▶ UI更新
└─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| 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` | モデル | カラムモデル |
| colored.rb | `app/models/column/colored.rb` | concern | 色のデフォルト値設定 |
| positioned.rb | `app/models/column/positioned.rb` | concern | position自動採番 |
| create.turbo_stream.erb | `app/views/boards/columns/create.turbo_stream.erb` | ビュー | 作成後のUI更新 |
| schema.rb | `db/schema.rb` | スキーマ | columnsテーブル定義（239-249行目） |
