# 機能設計書 54-検索クエリ履歴

## 概要

本ドキュメントは、Fizzyアプリケーションにおける検索クエリ履歴機能の詳細設計を記述する。

### 本機能の処理概要

最近の検索クエリを保存・表示することで、ユーザーが過去に行った検索を素早く再実行できるようにする機能である。

**業務上の目的・背景**：ユーザーは同じキーワードで繰り返し検索を行うことが多い。検索クエリ履歴を保存することで、再入力の手間を省き、効率的な情報検索を支援する。また、過去の検索を振り返ることで、どのような情報を探していたかを思い出すきっかけにもなる。

**機能の利用シーン**：検索画面にアクセスした際に、最近の検索クエリが一覧表示される。ユーザーはこの一覧から過去の検索クエリをクリックして、同じ検索を即座に実行できる。新しい検索を実行すると、そのクエリが履歴に追加される。

**主要な処理内容**：
1. ユーザーが検索を実行すると検索クエリを保存
2. 既存の同一クエリがある場合はupdated_atを更新
3. 検索画面表示時に最近の検索クエリ（最大10件）を取得・表示
4. 履歴から選択することで再検索を実行

**関連システム・外部連携**：特になし

**権限による制御**：ユーザー固有の検索履歴のみが表示される。他のユーザーの検索履歴は参照不可。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 32 | 検索画面 | 主画面 | 最近の検索クエリを表示 |

## 機能種別

CRUD操作（CREATE/UPDATE/READ）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| q | String | Yes | 検索クエリ | 空でないこと、サニタイズ処理適用 |

### 入力データソース

画面入力（検索フォーム）からの非同期リクエスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| - | - | 保存成功時はステータス200のみ |

### 出力先

- HTTPレスポンス（200 OK）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ POST /searches/queries
2. クエリパラメータ取得
   └─ params[:q]から検索クエリを取得
3. 検索クエリ保存
   └─ Current.user.remember_search(params[:q])
      3-1. 既存クエリの検索（find_or_create_by）
      3-2. 新規の場合は作成
      3-3. 既存の場合はtouchでupdated_atを更新
4. レスポンス返却
   └─ head :ok
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[クエリパラメータ取得]
    B --> C[remember_search呼び出し]
    C --> D{同一クエリ存在?}
    D -->|No| E[新規作成]
    D -->|Yes| F{前回と同じ?}
    F -->|Yes| G[何もしない]
    F -->|No| H[touchでupdated_at更新]
    E --> I[200 OK返却]
    G --> I
    H --> I
    I --> J[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-54-01 | 重複防止 | 同一クエリは重複して保存しない（find_or_create_by） | クエリ保存時 |
| BR-54-02 | 更新日時更新 | 既存クエリ使用時はupdated_atを更新して最新に | 既存クエリ使用時 |
| BR-54-03 | 表示件数制限 | 最近の検索クエリは最大10件まで表示 | 検索画面表示時 |
| BR-54-04 | クエリサニタイズ | 無効な文字を除去、引用符のバランスを調整 | クエリ保存時 |
| BR-54-05 | 空クエリ拒否 | 空のクエリは保存しない（バリデーションエラー） | クエリ保存時 |

### 計算ロジック

該当なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| クエリ保存 | search_queries | INSERT/UPDATE | 検索クエリの新規作成または更新 |
| クエリ取得 | search_queries | SELECT | 最近の検索クエリ取得 |

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

#### search_queries

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成UUID | 主キー |
| INSERT | account_id | Current.user.account_id | ユーザーのアカウント |
| INSERT | user_id | Current.user.id | ユーザーID |
| INSERT | terms | サニタイズ済みクエリ | 最大2000文字 |
| INSERT | created_at | 現在時刻 | - |
| INSERT | updated_at | 現在時刻 | - |
| UPDATE | updated_at | 現在時刻 | touchで更新 |
| SELECT | * | user_id = Current.user.id | updated_atの降順、最大10件 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | バリデーションエラー | クエリが空 | 無効なSearch::Queryとしてスキップ |

### リトライ仕様

リトライは不要

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

find_or_create_byは単一トランザクションで実行される。

## パフォーマンス要件

- レスポンス時間: 100ms以内
- インデックス: user_id + terms、user_id + updated_at

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

- ログイン必須（ApplicationControllerで制御）
- ユーザー固有の検索履歴のみ参照・更新可能
- CSRFトークン検証（Railsデフォルト）
- クエリのサニタイズによるインジェクション対策

## 備考

- 検索履歴は自動的に蓄積され、明示的な削除機能は提供されていない
- 古い履歴は新しい検索により自動的に一覧から押し出される（最新10件表示）

---

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

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

### 推奨読解順序

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

まず、検索クエリのデータモデルとバリデーションを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | query.rb | `app/models/search/query.rb` | Search::Queryモデル、バリデーション、サニタイズ |

**読解のコツ**:
- `validates :terms, presence: true`で空クエリを拒否
- `before_validation :sanitize_terms`でクエリをサニタイズ
- 無効な文字除去とアンバランスな引用符処理を実施

**主要処理フロー**:
- **5行目**: termsの存在バリデーション
- **6行目**: before_validationでサニタイズ実行
- **21-32行目**: sanitizeメソッド（クエリのクリーンアップ）
- **35-36行目**: remove_invalid_search_characters（無効文字除去）
- **39-44行目**: remove_unbalanced_quotes（引用符バランス調整）

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

リクエストを受け取るコントローラーの処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | queries_controller.rb | `app/controllers/searches/queries_controller.rb` | createアクションの処理フロー |

**主要処理フロー**:
- **2-4行目**: createアクション（remember_search呼び出しと200 OK返却）

**読解のコツ**:
- シンプルに`head :ok`でレスポンスを返すのみ
- 実際の保存ロジックはUser::Searcherモジュールに委譲

#### Step 3: ユーザーの検索履歴管理を理解する

User::Searcherコンサーンの履歴保存ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | searcher.rb | `app/models/user/searcher.rb` | remember_searchメソッド |

**主要処理フロー**:
- **5行目**: has_many :search_queriesアソシエーション
- **12-16行目**: remember_searchメソッド（find_or_create_byとtouch）

**読解のコツ**:
- `find_or_create_by(terms: terms)`で重複を防止
- `touch`でupdated_atを更新（新規作成時はスキップ）
- `previously_new_record?`で新規作成かどうかを判定

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

```
Searches::QueriesController#create
    │
    └─ Current.user.remember_search(params[:q])
           │
           └─ User::Searcher#remember_search
                  │
                  ├─ search_queries.find_or_create_by(terms: terms)
                  │      │
                  │      └─ Search::Query.new/find
                  │             │
                  │             └─ [before_validation] sanitize_terms
                  │                    │
                  │                    ├─ remove_invalid_search_characters
                  │                    └─ remove_unbalanced_quotes
                  │
                  └─ search_query.touch (既存の場合)
```

### データフロー図

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

q（クエリ）    ───▶ remember_search       ───▶ Search::Query
                       │
                       ▼
                 find_or_create_by
                       │
            ┌──────────┴──────────┐
            ▼                     ▼
       新規作成時            既存の場合
            │                     │
            ▼                     ▼
    Search::Query           touch実行
    (INSERT)              (UPDATE updated_at)
            │                     │
            └──────────┬──────────┘
                       ▼
                    200 OK         ───▶ ブラウザ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| queries_controller.rb | `app/controllers/searches/queries_controller.rb` | コントローラー | 検索クエリ保存のエンドポイント |
| query.rb | `app/models/search/query.rb` | モデル | 検索クエリのデータモデル |
| searcher.rb | `app/models/user/searcher.rb` | コンサーン | ユーザーの検索機能・履歴管理 |
| searches_controller.rb | `app/controllers/searches_controller.rb` | コントローラー | 検索履歴の表示（@recent_search_queries） |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
| schema.rb | `db/schema.rb` | スキーマ | search_queriesテーブル定義 |
