0
✦ 品質
🛡 セキュリティ
⚙ 保守性
📋 概要
Ruby on Rails 7 + Stimulus.js で構築された育成型タスク管理Webアプリ「RewardMe」。タスク完了でキャラクターが進化し、ガチャ・ミニゲーム5種・18種類のマイルームテーマなどゲーム要素をフルスクラッチ実装している。
🎯 今すぐ直すべき3点
-
app/javascript/controllers/match_game_controller.js 16180文字のコントローラーが単一ファイルに集約されており、ゲームロジック・UI操作・タイマー管理・スコア計算が混在している。GameTimer・ScoreManager・BoardRenderer などのクラスに責務を分割し、各ファイル200行以内に収める。↗
-
app/javascript/controllers/gacha_controller.js pull() メソッド内の fetch エラー処理が .catch(() => { ... }) のみで、HTTPステータスエラー(4xx/5xx)を r.json() 失敗まで検知できない。r.ok チェックを追加し、data.error 以外のサーバーエラーも適切にユーザーへ通知する。↗
-
app/models/user.rb ribbon_exp・ribbon_exp_percent・next_stage_tasks など completed_count を参照する計算メソッドが User モデルに7つ以上直書きされており、モデルが肥大化している。RibbonProgress などの Value Object / PORO に抽出し、User モデルの責務をデータ永続化に限定する。↗
🔍 検出された問題 (6件)
🟥 Critical
📄 app/javascript/controllers/match_game_controller.js
Stimulusコントローラーの巨大化・責務混在
match_game_controller.js(16180文字)・ribbon_controller.js(7251文字)・potion_game_controller.js(10741文字)・tap_game_controller.js(9486文字)・puzzle_controller.js(9969文字)がいずれも単一ファイルに全ロジックを詰め込んでいる。ゲームロジック・DOM操作・タイマー・アニメーション・サーバー通信が混在しており、バグ修正時の影響範囲の特定が困難で、テストも書きにくい状態になっている。
🟧 Warning
📄 app/javascript/controllers/gacha_controller.js
gacha_controller.js の fetch エラーハンドリング不備
pull() の fetch チェーンで HTTP 4xx/5xx レスポンスを r.ok で検証せず r.json() を直接呼んでいるため、サーバーエラー時に JSON パース失敗が .catch() に流れてユーザーへの通知が「エラーが発生したよ」すら出ない可能性がある。コイン消費を伴う操作でサーバーエラーが無音で失敗すると、ユーザーがコインを失ったと誤認するリスクがある。
🟧 Warning
📄 app/models/user.rb
User モデルへのゲームロジック集中による肥大化
app/models/user.rb に ribbon_level・ribbon_stage・ribbon_exp・ribbon_exp_percent・ribbon_exp_max・next_stage_tasks・ribbon_message・ribbon_stage_image・badge など育成ゲームの計算ロジックが直接実装されており、ファイルが7507文字に達している。ActiveRecord モデルがデータ永続化とゲームドメインロジックの両方を担うことで、単体テストの記述が困難になり、ロジック変更時の影響範囲が広がる。
🟧 Warning
📄 app/javascript/controllers/ribbon_controller.js
ribbon_controller.js への大量のハードコードされた文字列定数
ROOM_MESSAGES・SPECIAL_COMBOS に18部屋分・数十パターンのセリフ文字列がJavaScriptファイルにハードコードされている。セリフの追加・修正のたびにJSファイルを変更してデプロイが必要になり、非エンジニアによるコンテンツ管理が不可能な構造になっている。
🟧 Warning
📄 app/models/letter.rb
app/models/letter.rb がActiveRecordを継承しないPOROでカタログをハードコード
Letter クラスがDBを持たず CATALOG 定数にすべてのレター内容をハードコードしており、コンテンツ追加のたびにコードデプロイが必要になる。また received_at に '2026-06-01' などの将来日付がハードコードされており、日付ロジックの変更が困難である。
🟨 Suggestion
📄 app/javascript/controllers/puzzle_controller.js
puzzle_controller.js の selectPuzzle で catch が silent
selectPuzzle() の catch ブロックが `/* silent */` でエラーを完全に握りつぶしており、ネットワーク障害時にユーザーへのフィードバックが一切ない。パズル絵柄の選択失敗が無音で起きると、ユーザーは意図しない絵柄でゲームを開始してしまう可能性がある。
✅ 良い点
- app/models/user.rb の completed_count を唯一のソース・オブ・トゥルースとし、レベル・EXP・ステージ・称号をすべて計算で導出する設計は、状態の二重管理による不整合を構造的に排除しており、データ整合性の観点で優れている。
- Dockerfile がマルチステージビルド(builder / runtime)を採用し、ビルド時のみ使用する dummy SECRET_KEY_BASE が最終イメージに残らない構成になっている点は、シークレット漏洩リスクを低減する実践的なセキュリティ対策である。
- app/models/user.rb の create_guest! / cleanup_guests! がゲストユーザーのライフサイクルを明示的に管理し、dependent: :destroy による関連タスクの連鎖削除も担保されており、孤立レコードの蓄積を防ぐ設計になっている。
🔧 リファクタ提案
- app/models/user.rb のリボン育成計算(ribbon_stage・ribbon_exp・ribbon_exp_percent など)を RibbonProgress PORO に抽出し、`RibbonProgress.new(completed_count).stage` のように呼び出す形にする。User モデルはデータ永続化とゲスト管理のみに責務を絞る。
- match_game_controller.js・tap_game_controller.js などの巨大コントローラーを、GameTimer(タイマー管理)・ScoreBoard(スコア表示)・BoardRenderer(盤面描画)などの小クラスに分割し、Stimulus コントローラーはそれらのオーケストレーターとして機能させる。
- ribbon_controller.js の ROOM_MESSAGES・SPECIAL_COMBOS をサーバーサイドで管理し(YAMLファイルまたはDBテーブル)、Stimulus コントローラー初期化時に data 属性経由で受け取る設計に変更することで、コンテンツ変更をデプロイ不要にする。
🔒 セキュリティ注意点
- app/javascript/controllers/puzzle_controller.js の selectPuzzle() で CSRF トークンを `document.querySelector('meta[name="csrf-token"]').content` から取得しており、Rails の標準的な CSRF 対策は機能しているが、fetch レスポンスの r.ok チェックが欠如しているため、認可エラー(401/403)がサイレントに失敗するリスクがある。全 fetch 呼び出しに r.ok ガードを追加すること。
- app/models/user.rb の create_guest! で生成されるゲストアカウントは SecureRandom.hex(16) のパスワードを使用しており推測困難だが、cleanup_guests! の実行タイミング(Cronジョブ等)が確認できないため、長期間放置されたゲストアカウントが蓄積するリスクがある。定期実行の仕組みを明示的にドキュメント化またはコード化すること。
📤 PRコメント出力
このレビューをGitHub PRコメントとして貼り付けられるMarkdown形式で出力します。