JA Architecture Data Flow - hiraishikentaro/rails-factorybot-jump GitHub Wiki
アーキテクチャ: データフロー
データフロー概要
Rails FactoryBot Jump は、初期化、リンク生成、キャッシュ更新、ユーザーナビゲーションという複数の異なるフローを通してデータを処理します。各フローはパフォーマンスと信頼性のために最適化されています。
graph TB
subgraph "データソース"
A[ファクトリファイル]
B[テストファイル]
C[設定]
end
subgraph "処理レイヤー"
D[ファイルスキャナー]
E[パターン検出器]
F[キャッシュビルダー]
end
subgraph "データストレージ"
G[ファクトリキャッシュ]
H[トレイトキャッシュ]
I[ファイルURIキャッシュ]
end
subgraph "出力レイヤー"
J[ドキュメントリンク]
K[ナビゲーションコマンド]
L[ユーザーインターフェース]
end
A --> D
B --> E
C --> D
D --> F
E --> J
F --> G
F --> H
F --> I
G --> J
H --> J
J --> K
K --> L
主要なデータフロー
1. 拡張機能初期化フロー
トリガー: VSCode が Ruby ファイルを初めて開いたとき
sequenceDiagram
participant User as ユーザー
participant VSCode
participant Extension as 拡張機能
participant Provider as プロバイダー
participant FileSystem as ファイルシステム
participant Cache as キャッシュ
User->>VSCode: Rubyファイルを開く
VSCode->>Extension: activate()
Extension->>Provider: new FactoryLinkProvider()
Extension->>VSCode: registerDocumentLinkProvider()
Extension->>FileSystem: createFileSystemWatcher()
Note over Provider: 遅延初期化
VSCode->>Provider: provideDocumentLinks()
Provider->>Cache: 初期化済みかチェック
Cache-->>Provider: 未初期化
Provider->>FileSystem: getConfiguration()
FileSystem-->>Provider: factoryPaths
Provider->>FileSystem: findFiles(factoryPaths)
FileSystem-->>Provider: ファクトリファイルURI
Provider->>Cache: buildFactoryCache()
Provider->>Cache: buildTraitCache()
Cache-->>Provider: キャッシュ準備完了
Provider-->>VSCode: DocumentLink[]
データ変換:
- 設定 → ファクトリファイルパス(glob パターン)
- ファイルパス → ファイル URI(VSCode workspace API)
- ファイル URI → ファイル内容(テキスト読み込み)
- ファイル内容 → ファクトリ定義(正規表現解析)
- ファクトリ定義 → キャッシュエントリ(Map 構造)
ソース: src/providers/factoryLinkProvider.ts
2. ファクトリキャッシュ構築フロー
入力: ファクトリファイル URI
出力: ファクトリとトレイトキャッシュ
graph LR
A[ファクトリファイルURI] --> B[ファイル内容読み込み]
B --> C[ファクトリ定義抽出]
C --> D[ファクトリ名解析]
D --> E[行番号計算]
E --> F[ファクトリキャッシュに保存]
B --> G[トレイト定義抽出]
G --> H[トレイト名解析]
H --> I[ファクトリと関連付け]
I --> J[トレイトキャッシュに保存]
ファクトリ定義解析:
// ファクトリ検出用正規表現パターン
const factoryPattern = /factory\s+:([a-zA-Z0-9_]+)\b/g
const traitPattern = /trait\s+:([a-zA-Z0-9_]+)\s+do/g
// データ変換
factoryMatch → {
name: string, // ファクトリ名(例: "user")
uri: vscode.Uri, // ファイル場所
lineNumber: number // ファイル内の行
}
traitMatch → {
name: string, // トレイト名(例: "admin")
factory: string, // 親ファクトリ(例: "user")
uri: vscode.Uri, // ファイル場所
lineNumber: number // ファイル内の行
}
3. リンク生成フロー
トリガー: ユーザーがテストファイル内のファクトリ呼び出しにマウスオーバーしたとき
sequenceDiagram
participant User as ユーザー
participant VSCode
participant Provider as プロバイダー
participant PatternDetector as パターン検出器
participant Cache as キャッシュ
User->>VSCode: ファクトリ呼び出しにマウスオーバー
VSCode->>Provider: provideDocumentLinks(document)
Provider->>PatternDetector: findFactoryCalls(text)
PatternDetector->>PatternDetector: 正規表現パターン適用
PatternDetector-->>Provider: ファクトリ呼び出しマッチ
loop 各ファクトリ呼び出し
Provider->>Cache: lookup(factoryName)
Cache-->>Provider: ファクトリ定義
Provider->>Provider: createDocumentLink()
end
Provider-->>VSCode: DocumentLink[]
VSCode-->>User: クリック可能リンクを表示
パターン検出プロセス:
// ファクトリ呼び出し検出正規表現
const factoryCallPattern = /(?:create|create_list|build|build_list|build_stubbed|build_stubbed_list)\s*(?:\(\s*)?((:[a-zA-Z0-9_]+)(?:\s*,\s*(:[a-zA-Z0-9_]+))*)/g
// データ抽出
textMatch → {
factoryName: string, // 例: "user"
traits: string[], // 例: ["admin", "verified"]
startPos: number, // ドキュメント内の文字位置
endPos: number // 終了文字位置
}
// キャッシュ検索
factoryName → {
uri: vscode.Uri, // ターゲットファイル
lineNumber: number // ターゲット行
}
// リンク生成
cacheEntry → vscode.DocumentLink {
range: vscode.Range, // クリック可能テキスト範囲
target: vscode.Uri // ナビゲーションコマンドURI
}
4. ファイル変更検出フロー
トリガー: ファクトリファイルが作成、変更、または削除されたとき
graph TB
A[ファイルシステム変更] --> B[ファイルウォッチャーイベント]
B --> C{イベント種別}
C -->|作成| D[キャッシュに追加]
C -->|変更| E[キャッシュエントリ更新]
C -->|削除| F[キャッシュから削除]
D --> G[影響を受けるキャッシュを再構築]
E --> G
F --> G
G --> H[ドキュメントリンクを無効化]
H --> I[VSCodeがリンクを更新]
キャッシュ更新プロセス:
// ファイル変更イベント
FileSystemEvent → {
type: 'create' | 'change' | 'delete',
uri: vscode.Uri
}
// キャッシュ無効化戦略
if (event.type === 'delete') {
// このファイルのすべてのエントリを削除
removeFileFromCaches(event.uri)
} else {
// このファイルのエントリを再解析して更新
reparseFactoryFile(event.uri)
}
5. ナビゲーションコマンドフロー
トリガー: ユーザーがファクトリリンクをクリックしたとき
sequenceDiagram
participant User as ユーザー
participant VSCode
participant Extension as 拡張機能
participant FileSystem as ファイルシステム
User->>VSCode: ファクトリリンクをクリック
VSCode->>Extension: executeCommand("gotoLine", args)
Extension->>FileSystem: openTextDocument(uri)
FileSystem-->>Extension: TextDocument
Extension->>VSCode: showTextDocument(document)
VSCode-->>Extension: TextEditor
Extension->>VSCode: setSelection(lineNumber)
Extension->>VSCode: revealRange(lineNumber)
VSCode-->>User: ファクトリ定義にナビゲート
ナビゲーションデータフロー:
// クリックイベントデータ
DocumentLink.target → vscode.Uri {
scheme: "command",
path: "rails-factorybot-jump.gotoLine",
query: JSON.stringify({
uri: string, // ターゲットファイルURI
lineNumber: number // ターゲット行番号
})
}
// コマンド実行
commandArgs → {
uri: string, // ターゲットファイルパス
lineNumber: number // 0ベースの行番号
}
// VSCode操作
uri → vscode.TextDocument → vscode.TextEditor → vscode.Selection
データ構造
1. キャッシュデータ構造
ファクトリキャッシュ:
Map<string, FactoryDefinition>
interface FactoryDefinition {
uri: vscode.Uri // ファクトリを含むファイル
lineNumber: number // 0ベースの行番号
}
// エントリ例
{
"user" => { uri: "file:///spec/factories/users.rb", lineNumber: 1 },
"post" => { uri: "file:///spec/factories/posts.rb", lineNumber: 0 }
}
トレイトキャッシュ:
Map<string, TraitDefinition>
interface TraitDefinition {
uri: vscode.Uri // トレイトを含むファイル
lineNumber: number // 0ベースの行番号
factory: string // 親ファクトリ名
}
// エントリ例(キー形式: "factory:trait")
{
"user:admin" => { uri: "file:///spec/factories/users.rb", lineNumber: 5, factory: "user" },
"post:published" => { uri: "file:///spec/factories/posts.rb", lineNumber: 8, factory: "post" }
}
2. 中間データ構造
パターンマッチ結果:
interface FactoryCallMatch {
factoryName: string; // ':'なしのファクトリ名
traits: string[]; // ':'なしのトレイト名
range: vscode.Range; // ドキュメント内のテキスト範囲
fullMatch: string; // 完全一致テキスト
}
ファイル解析結果:
interface ParsedFactory {
name: string; // ファクトリ名
lineNumber: number; // 定義行
traits: ParsedTrait[]; // ファクトリ内のトレイト
}
interface ParsedTrait {
name: string; // トレイト名
lineNumber: number; // 定義行
}
パフォーマンス最適化
1. 遅延読み込み戦略
graph LR
A[拡張機能アクティベーション] --> B{初回リンク要求?}
B -->|はい| C[キャッシュ初期化]
B -->|いいえ| D[既存キャッシュ使用]
C --> E[ファクトリファイルスキャン]
E --> F[キャッシュ構築]
F --> G[リンク生成]
D --> G
メリット:
- 拡張機能アクティベーションの高速化
- 未使用機能のメモリ使用量削減
- ユーザー体験の向上
2. 増分キャッシュ更新
graph TB
A[ファイル変更イベント] --> B{影響を受けるファイル?}
B -->|はい| C[変更されたファイルのみ解析]
B -->|いいえ| D[アクションなし]
C --> E[特定のキャッシュエントリを更新]
E --> F[影響を受けないエントリを保持]
メリット:
- 変更時の最小限の再処理
- キャッシュ一貫性の維持
- CPU 使用量の削減
3. 効率的なデータアクセスパターン
O(1)キャッシュ検索:
// 直接ハッシュマップアクセス
const factory = factoryCache.get(factoryName);
const trait = traitCache.get(`${factoryName}:${traitName}`);
バッチ操作:
// ドキュメント内のすべてのファクトリ呼び出しを一度に処理
const allMatches = findAllFactoryCalls(document.getText());
const links = allMatches.map((match) => createDocumentLink(match));
データフローでのエラーハンドリング
1. ファイルシステムエラーハンドリング
graph TB
A[ファイル操作] --> B{成功?}
B -->|はい| C[データ処理]
B -->|いいえ| D[エラーログ]
D --> E[利用可能データで継続]
C --> F[キャッシュ更新]
E --> G[優雅な機能低下]
2. 解析エラー回復
// エラー回復機能付きの堅牢な解析
try {
const factories = parseFactoryFile(fileContent);
updateCache(factories);
} catch (error) {
console.warn(`ファクトリファイルの解析に失敗: ${file.path}`, error);
// 他のファイルで継続
}
3. キャッシュ一貫性
一貫性保証:
- アトミックキャッシュ更新(すべてまたは何も実行しない)
- 解析失敗時のロールバック
- ファイル監視による最終的一貫性
データフロー監視
1. パフォーマンスメトリクス
主要メトリクス:
- キャッシュ構築時間
- リンク生成時間
- ファイル解析時間
- メモリ使用量
2. デバッグ情報
データフロートレース:
- ファイル発見イベント
- キャッシュヒット/ミス率
- 解析成功/失敗率
- リンク生成数
このデータフローアーキテクチャは、優秀なパフォーマンスとユーザー体験を提供しながら、効率的で信頼性が高く、保守可能なファクトリナビゲーションを確保します。