Skip to content

管理画面にユーザーの学習進捗コピー機能を追加#8797

Merged
komagata merged 23 commits intomainfrom
feature/copy-done-practice
Jun 18, 2025
Merged

管理画面にユーザーの学習進捗コピー機能を追加#8797
komagata merged 23 commits intomainfrom
feature/copy-done-practice

Conversation

@komagata
Copy link
Copy Markdown
Member

@komagata komagata commented Jun 13, 2025

概要

管理者がユーザーのRailsエンジニアコースでの学習進捗をReスキル講座認定版にコピーできる機能を追加しました。

主な変更内容

  • Presenterパターンの導入: 表示ロジックを分離し、ActiveRecord Relationを活用した効率的なデータ取得
  • Query Objectパターンの導入: CompletedLearningsQueryクラスでクエリロジックを分離
  • 進捗マイグレーション機能: PracticeProgressMigratorクラスで学習状況とプロダクトデータのコピー処理
  • 管理画面の追加: ユーザー毎の完了プラクティス一覧と個別・一括コピー機能

技術的な改善

  • rails-patternsgemを導入してQuery Objectパターンを実装
  • Presenterパターンでビューロジックを集約
  • N+1クエリ問題を回避する効率的なデータ取得
  • 適切な責任分離とコードの可読性向上

テスト計画

  • ユニットテスト: PracticeProgressMigratorクラス
  • システムテスト: 管理画面の操作フロー
  • 個別コピー機能の動作確認
  • 一括コピー機能の動作確認

Summary by CodeRabbit

  • 新機能

    • ユーザーのプラクティス進捗データをコピー先プラクティスへ移行する機能を追加しました。
    • プラクティス進捗やコピー状態を集約的に表示・管理できる新しいプレゼンターを追加しました。
    • プロダクト間でチェック記録をコピーする機能を追加しました。
    • プラクティス進捗のコピー処理を行う複数のインタラクターを導入しました(学習記録、プロダクト、チェックのコピー)。
    • 管理画面にてユーザーのプラクティス進捗を一覧表示し、個別および一括で進捗コピーを実行できる機能を追加しました。
    • プラクティスのコピー元・コピー先を表現する関連付けをモデルに追加しました。
    • 管理画面のプラクティス進捗表示に対応するビューを新規作成しました。
    • Railsコースの定数定義と取得メソッドを追加しました。
    • 管理画面のプラクティス進捗関連のルーティングを追加しました。
    • 英語の複数形処理において「progress」を不可算名詞として設定しました。
    • アプリケーションの設定にプレゼンターの自動読み込みパスを追加しました。
    • 新たに導入したgem「interactor」と「rails-patterns」を追加しました。
  • テスト

    • チェックコピー機能の動作を検証するテストを追加しました。
    • 学習記録、プロダクト、プラクティス進捗コピーの各インタラクターに対する単体テストを追加しました。
    • プラクティス進捗移行処理の動作検証テストを追加しました。
    • 管理画面のプラクティス進捗表示およびコピー操作のシステムテストを追加しました。

Copilot AI review requested due to automatic review settings June 13, 2025 17:51

This comment was marked as outdated.

@fjordllc fjordllc deleted a comment from Copilot AI Jun 14, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jun 14, 2025

"""

Walkthrough

新たにユーザーのプラクティス進捗やチェックのコピーを担うクラス・サービス・プレゼンターが追加されました。進捗の個別・一括移行、コピー状況の可視化、チェックの重複防止付きコピーなどを担うロジックと、それらのテストが実装されています。

Changes

ファイル/グループ 変更概要
app/models/practice_progress_migrator.rb ユーザーのプラクティス進捗データをコピーする PracticeProgressMigrator クラスを新規追加。個別・一括移行メソッドを実装し、進捗コピーの成否を管理。
app/presenters/practice_progress_presenter.rb プラクティス進捗・コピー状況・移行候補の情報取得や可視化を担う PracticeProgressPresenter クラスを新規追加。効率的なデータ取得と状態判定を実装。
app/interactors/copy_check.rb チェック(Check)データを元プロダクトからコピー先プロダクトへ重複防止付きでコピーする CopyCheck インタラクターを新規追加。コピー処理の成功・失敗管理やメッセージ設定を含む。
app/interactors/copy_learning.rb 学習記録(Learning)を元プラクティスからコピー先プラクティスへコピーする CopyLearning インタラクターを新規追加。入力検証や既存コピーのスキップ処理を含む。
app/interactors/copy_product.rb プラクティスのプロダクトを元からコピー先へコピーする CopyProduct インタラクターを新規追加。既存コピーの有無判定やエラーハンドリングを実装。
app/interactors/copy_practice_progress.rb 学習、プロダクト、チェックのコピーをまとめて行う CopyPracticeProgress インタラクターを新規追加。トランザクション内で各コピー処理を連携。
app/controllers/admin/users/practice_progress_controller.rb ユーザーのプラクティス進捗表示と個別コピー操作を担う管理者用コントローラーを新規追加。パラメータ検証やリダイレクト処理を実装。
app/controllers/admin/users/practice_progress_batches_controller.rb ユーザーの全プラクティス進捗一括コピーを行う管理者用コントローラーを新規追加。コピー処理の成否に応じたリダイレクトを実装。
app/models/practice.rb プラクティスの自己参照関連付けを追加し、コピー元・コピー先プラクティスの関係を表現。
app/models/user.rb practice_ids_skipped メソッドの位置を移動(機能は変更なし)。
app/models/course.rb 定数 RAILS_COURSE を追加し、DEFAULT_COURSE をそれに置き換え。rails_course クラスメソッドを追加。
app/queries/completed_learnings_query.rb 指定コースの完了済み学習記録を取得するクエリクラス CompletedLearningsQuery を新規追加。
app/views/admin/users/practice_progress/show.html.slim 管理画面のユーザーのプラクティス進捗表示ビューを新規追加。オリジナルとコピー先プラクティスの進捗状況表示、コピー操作ボタンを実装。
config/application.rb app/presenters ディレクトリをイージーロードパスに追加。
config/initializers/inflections.rb 英語の不可算名詞として "progress" を追加。
config/routes/admin.rb 管理者用ユーザーリソースにプラクティス進捗関連のルーティング(個別および一括コピー用)を追加。
Gemfile interactorrails-patterns の2つの新規gemを追加。
test/interactors/copy_check_test.rb CopyCheck インタラクターの単体テストを新規追加。正常系・重複スキップ・例外発生時の動作を検証。
test/interactors/copy_learning_test.rb CopyLearning インタラクターの単体テストを新規追加。正常系、既存コピースキップ、異常系を網羅。
test/interactors/copy_product_test.rb CopyProduct インタラクターの単体テストを新規追加。正常系、既存コピースキップ、例外発生時の動作を検証。
test/interactors/copy_practice_progress_test.rb CopyPracticeProgress インタラクターの単体テストを新規追加。各コピー処理の連携や失敗時の挙動を検証。
test/models/practice_progress_migrator_test.rb PracticeProgressMigrator クラスの単体テストを新規追加。個別・一括移行の成功・失敗ケースを検証。
test/models/practice_test.rb Practice モデルの source_id による自己参照禁止バリデーションのテストを追加。
test/system/admin/users/practice_progress_test.rb 管理画面のユーザーのプラクティス進捗表示とコピー操作のシステムテストを新規追加。UI表示やコピー成功の動作を検証。
.github/copilot/guidelines.yml Pull Requestレビュー時の言語を日本語に限定し、丁寧なコメントを求めるCopilotガイドライン設定ファイルを追加。
db/migrate/20250618142946_add_index_on_source_id_to_practices.rb practices テーブルの source_id カラムにインデックスを追加するマイグレーションを新規追加。
db/migrate/20250618143741_add_unique_index_to_users_email_and_login_name.rb users テーブルの emaillogin_name にユニークインデックスを追加するマイグレーションを新規追加。
db/migrate/20250618144325_add_foreign_key_to_practices_source_id.rb practices テーブルの source_id に外部キー制約を追加するマイグレーションを新規追加。
db/schema.rb スキーマバージョン更新。practicessource_id にインデックスと外部キー制約追加。coursessummary カラム位置変更。

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PracticeProgressMigrator
    participant PracticeProgressPresenter
    participant CopyPracticeProgress
    participant CopyCheck

    User->>PracticeProgressMigrator: migrate_all()
    PracticeProgressMigrator->>PracticeProgressPresenter: completed_practices
    loop 各完了プラクティス
        PracticeProgressMigrator->>CopyPracticeProgress: call(original, copied)
        CopyPracticeProgress->>CopyCheck: call(original_product, copied_product)
        CopyCheck-->>CopyPracticeProgress: checks copied/skipped
        CopyPracticeProgress-->>PracticeProgressMigrator: progress copied
    end
    PracticeProgressMigrator-->>User: migration result
Loading

Poem

うさぎの耳で進捗を聴く、
コピーもチェックもぴょんと跳ぶ。
新しい道をなぞりながら、
データも笑顔も重複なし!
テストもばっちり、安心の春。
ぴょんぴょん進化、コードの森で。
(∩・×・∩)✨
"""

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 RuboCop (1.75.5)
db/migrate/20250618144325_add_foreign_key_to_practices_source_id.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:309:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:82:in block in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:76:in each_pair'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader.rb:66:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_store.rb:29:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:160:in act_on_options'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:47:in block in run' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:81:in profile_if_needed'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:43:in run' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/exe/rubocop:19:in <top (required)>'
/usr/local/bin/rubocop:25:in load' /usr/local/bin/rubocop:25:in

'

db/schema.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:309:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:82:in block in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:76:in each_pair'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader.rb:66:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_store.rb:29:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:160:in act_on_options'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:47:in block in run' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:81:in profile_if_needed'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:43:in run' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/exe/rubocop:19:in <top (required)>'
/usr/local/bin/rubocop:25:in load' /usr/local/bin/rubocop:25:in

'

test/models/practice_test.rb

rubocop-minitest extension supports plugin, specify plugins: rubocop-minitest instead of require: rubocop-minitest in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
rubocop-capybara extension supports plugin, specify plugins: rubocop-capybara instead of require: rubocop-capybara in /.rubocop.yml.
For more information, see https://docs.rubocop.org/rubocop/plugin_migration_guide.html.
Unable to find gem rubocop-fjord; is the gem installed? Gem::MissingSpecError
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:309:in rescue in gem_config_path' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:293:in gem_config_path'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:84:in block (2 levels) in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:82:in reverse_each'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:82:in block in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:76:in each_pair'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader_resolver.rb:76:in resolve_inheritance_from_gems' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_loader.rb:66:in load_file'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/config_store.rb:29:in options_config=' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:160:in act_on_options'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:47:in block in run' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:81:in profile_if_needed'
/var/lib/gems/3.1.0/gems/rubocop-1.75.8/lib/rubocop/cli.rb:43:in run' /var/lib/gems/3.1.0/gems/rubocop-1.75.8/exe/rubocop:19:in <top (required)>'
/usr/local/bin/rubocop:25:in load' /usr/local/bin/rubocop:25:in

'

  • 1 others
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (10)
Gemfile (1)

65-65: バージョンを固定して再現性を担保しましょう

rails-patterns をバージョン指定なしで追加すると、将来の gem 更新による挙動変化や互換性問題を CI/本番環境で拾いづらくなります。~> などで最小限のバージョンを固定しておくことを推奨します。

-gem 'rails-patterns'
+gem 'rails-patterns', '~> 0.2'
config/application.rb (1)

24-26: eager_load 設定の統一を検討

config.paths.add "app/presenters", eager_load: true は動作上問題ありませんが、Zeitwerk では config.eager_load_paths << Rails.root.join('app/presenters') を用いる例が一般的です。
既存の "lib" ディレクトリ設定と書式を合わせることで可読性が向上します。

app/models/course.rb (2)

4-6: DEFAULT_COURSE のエイリアス化が二重管理になりやすい

DEFAULT_COURSE = RAILS_COURSE として別名を用意していますが、今後タイトルが変わった際に両定数を更新し忘れるリスクがあります。

  1. RAILS_COURSE を削除して DEFAULT_COURSE だけを使用する
  2. あるいは DEFAULT_COURSE を廃止し、呼び出し側で Course.rails_course を利用

など、定数を一つに集約する設計をご検討ください。


19-21: rails_coursedefault_course の責務が重複

両メソッドとも実装が find_by(title: …) で実質同じです。メソッドを一本化して呼び出し側で適切に命名した方が可読性と保守性が高まります。

config/routes/admin.rb (1)

10-14: リソース名を複数形または resource に統一すると可読性が向上します
resources :practice_progress だと URL/ヘルパが ...practice_progress_index となり Rails の慣習的な複数形とズレます。

  • 1件ずつ扱うなら resource :practice_progress
  • 複数扱うなら resources :practice_progresses

に変更することを検討してください。既存コードへの影響はヘルパ名とパスのみです。

app/queries/completed_learnings_query.rb (1)

10-14: ハードコード文字列より enum を利用すると保守性が高まります
where(status: 'complete') は enum 名変更時に壊れます。

-.where(status: 'complete', courses_categories: { course: @course })
+.where(status: Learning.statuses[:complete], courses_categories: { course: @course })

また order('learnings.updated_at asc')order(updated_at: :asc) の方が ARel ベースで安全です。

app/models/practice.rb (1)

54-57: source_id に DB 制約・インデックスを追加することを推奨します
自己参照関連を多用するクエリ性能と整合性確保のため、practices.source_id への外部キー制約(ON DELETE SET NULL)とインデックスを追加してください。

app/views/admin/users/practice_progress/index.html.slim (1)

48-55: ビューでのロジック肥大化を抑えてください

ruby: ブロックで複数の変数を計算しており、ビューが読みづらくなっています。
Presenter かヘルパーに計算を委譲し、Slim テンプレートはできるだけ表示専用にすると保守性が向上します。

test/system/admin/users/practice_progress_test.rb (1)

47-50: コメントアウトされた期待は削除を検討してください

意図しない将来仕様を示唆する不要なコメントはテストの可読性を下げます。不要なら消してください。

app/presenters/practice_progress_presenter.rb (1)

13-26: includes のネストが複雑です

深いネストをそのまま書くと読み取りが困難になります。
スコープや private メソッドに分割し、目的ごとに includes_rails_course_associations など命名すると理解しやすくなります。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 024fd00 and 181afa3.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • .github/copilot/guidelines.yml (1 hunks)
  • Gemfile (1 hunks)
  • app/controllers/admin/users/practice_progress_controller.rb (1 hunks)
  • app/models/course.rb (2 hunks)
  • app/models/practice.rb (2 hunks)
  • app/models/practice_progress_migrator.rb (1 hunks)
  • app/models/user.rb (1 hunks)
  • app/presenters/practice_progress_presenter.rb (1 hunks)
  • app/queries/completed_learnings_query.rb (1 hunks)
  • app/views/admin/users/practice_progress/index.html.slim (1 hunks)
  • config/application.rb (1 hunks)
  • config/routes/admin.rb (1 hunks)
  • test/models/practice_progress_migrator_test.rb (1 hunks)
  • test/system/admin/users/practice_progress_test.rb (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
app/models/practice.rb (2)
app/models/course.rb (1)
  • has_many (3-22)
app/models/practices_movie.rb (1)
  • belongs_to (3-6)
app/models/practice_progress_migrator.rb (2)
app/presenters/practice_progress_presenter.rb (1)
  • completed_practices (13-26)
app/models/practice.rb (2)
  • learning (126-128)
  • status (94-104)
app/presenters/practice_progress_presenter.rb (1)
app/models/practice.rb (1)
  • learning (126-128)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test
🔇 Additional comments (4)
Gemfile (1)

65-65: Rails 6.1 / Ruby 3.1 との互換性確認を

rails-patterns は gem の歴史が浅く、Rails 7 系での利用例が多い印象です。Rails 6.1.7.10 & Ruby 3.1.6 環境での動作保証が公式に明記されているかご確認ください。
問題があればフォーク/バージョンピンでの対応も検討をお願いします。

.github/copilot/guidelines.yml (1)

1-13: ガイドライン追加を確認しました

日本語レビューの指針が明確になり、運用ルールが共有しやすくなる点を評価します。

app/models/user.rb (1)

591-593: LGTM – メソッド移動のみ

practice_ids_skipped の実装変更はなく、N+1 も発生しません。問題ありません。

test/models/practice_progress_migrator_test.rb (1)

1-14: テストカバレッジが充実しており素晴らしいです
主要な分岐・エラーケースを網羅しており、回帰防止に寄与します。

Comment on lines +23 to +27
def bulk_copy
migrator = PracticeProgressMigrator.new(@user)
result = migrator.migrate_all

redirect_to admin_user_practice_progress_index_path(@user), notice: result[:message]
end

This comment was marked as outdated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodeRabbit 指摘点を修正しました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@komagata ありがとうございます!bulk_copy メソッドでの成否判定の修正、お疲れさまでした。これで管理者にとってより分かりやすいフィードバックが提供されますね。👍

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/controllers/admin/users/practice_progress_batches_controller.rb (1)

15-17: find 例外の扱いを検討してください

User.find が見つからない場合 ActiveRecord::RecordNotFound が発生し、管理画面全体が 404 レンダリングされます。管理者向けとはいえ、専用のエラーページやメッセージを出したい場合は find_by + 早期 return か rescue ハンドリングを検討しても良いでしょう。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7ab8e8 and 309a229.

📒 Files selected for processing (5)
  • app/controllers/admin/users/practice_progress_batches_controller.rb (1 hunks)
  • app/controllers/admin/users/practice_progress_controller.rb (1 hunks)
  • app/views/admin/users/practice_progress/index.html.slim (1 hunks)
  • config/initializers/inflections.rb (1 hunks)
  • config/routes/admin.rb (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • config/initializers/inflections.rb
🚧 Files skipped from review as they are similar to previous changes (3)
  • config/routes/admin.rb
  • app/controllers/admin/users/practice_progress_controller.rb
  • app/views/admin/users/practice_progress/index.html.slim
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
app/views/admin/users/practice_progress/show.html.slim (2)

1-8: ページタイトルが汎用的すぎて意味が伝わりづらい

- title '管理ページ' だと画面固有の意図が読み取りづらく、SEO やブラウザタブの識別性も低いです。
例:'学習進捗コピー - 管理' など、機能を示すタイトルへ変更を検討してください。


91-97: rel 属性に noreferrer を追加するとより安全

target="_blank" に対し rel="noopener" は入っていますが、noreferrer も付与しておくとリファラ漏洩も防げます。

test/system/admin/users/practice_progress_test.rb (2)

417-485: テストコードが重複しており保守負荷が高い

同様の前準備(オリジナル/コピー作成・学習/提出物生成)が繰り返されています。
setup メソッドやヘルパーとして共通化すると読みやすくなります。


98-108: Time.current.to_i だけではタイトルの一意性が保証されない可能性

同一秒内に複数レコードを作成すると衝突します。SecureRandom.hex(4) などを併用して衝突耐性を高めると安心です。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6539bec and 309f951.

📒 Files selected for processing (7)
  • Gemfile (1 hunks)
  • app/controllers/admin/users/practice_progress_batches_controller.rb (1 hunks)
  • app/controllers/admin/users/practice_progress_controller.rb (1 hunks)
  • app/models/practice_progress_migrator.rb (1 hunks)
  • app/views/admin/users/practice_progress/show.html.slim (1 hunks)
  • config/routes/admin.rb (1 hunks)
  • test/system/admin/users/practice_progress_test.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • Gemfile
  • config/routes/admin.rb
  • app/controllers/admin/users/practice_progress_batches_controller.rb
  • app/models/practice_progress_migrator.rb
  • app/controllers/admin/users/practice_progress_controller.rb
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test
🔇 Additional comments (2)
app/views/admin/users/practice_progress/show.html.slim (1)

70-75:

Details

❌ Incorrect review comment

confirm ダイアログが Turbo 環境で発火しない恐れ

Rails 7 + Turbo を使用している場合 data: { confirm: '...' } は無視され、
data: { turbo_confirm: '...' } が必要です。UJS を併用していないか要確認。

- data: { confirm: '進捗をコピーしますか?' }
+ data: { turbo_confirm: '進捗をコピーしますか?' }

Turbo環境とUJS併用の有無を確認するため、以下のスクリプトを実行します。


🏁 Script executed:

#!/bin/bash
# Rails本体とturbo-railsの導入状況を確認
grep -E "gem ['\"]rails['\"]" Gemfile
grep -E "gem ['\"]turbo-rails['\"]" Gemfile || echo "turbo-rails not found in Gemfile"

# importmapでpinされているかを確認
grep -R "pin \"turbo-rails\"" config/importmap.rb || echo "config/importmap.rb に turbo-rails の pin 設定なし"

# JavaScript側のTurboおよびRails UJSのimport状況を確認
grep -R "import.*turbo" -n app/javascript || echo "app/javascript内に turbo の import 見つからず"
grep -R "import.*@rails/ujs" -n app/javascript || echo "app/javascript内に @rails/ujs の import 見つからず"

Length of output: 670


Turbo未導入のRails 6.1環境ではdata-confirmが有効です
Gemfileに turbo-rails がなく、JavaScriptにも Turbo が import されていないため、
data: { confirm: '…' } は Rails UJS の data-confirm 属性として動作します。
data-turbo-confirm への変更は不要です。

Likely an incorrect or invalid review comment.

test/system/admin/users/practice_progress_test.rb (1)

565-568: 複数同名リンクが存在すると誤クリックの可能性

click_link '全ての進捗をコピー' は最初にマッチしたリンクを押します。
将来的に同一文言が増えた場合にテストが不安定になるため、href: で限定するか within でスコープを絞ってください。

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
app/views/admin/users/practice_progress/show.html.slim (1)

48-55: N+1問題が解決されています

過去のレビューで指摘されたN+1問題は、Presenterパターンの導入とeager loadingにより適切に解決されています。learning.practiceはPresenterのcompleted_practicesメソッドで事前読み込みされており、その他のデータもキャッシュメカニズムにより効率的に取得されています。

🧹 Nitpick comments (4)
app/presenters/practice_progress_presenter.rb (4)

39-43: クエリのパフォーマンスを改善できます

completed_practices.pluck(:practice_id)の結果をインスタンス変数にキャッシュすることで、複数のメソッドで同じpluck操作を繰り返すのを避けられます。

+  def practice_ids
+    @practice_ids ||= completed_practices.pluck(:practice_id)
+  end
+
   def migration_candidates
-    ids = completed_practices.pluck(:practice_id)
+    ids = practice_ids
     completed_practices.joins(:practice).where(practices: { source_id: nil })
                        .where.not(practice_id: Practice.where(source_id: ids).pluck(:source_id))
   end

60-60: 前述のpractice_idsキャッシュを活用してください

ここでもcompleted_practices.pluck(:practice_id)を使用しています。前述のpractice_idsメソッドを使用することで一貫性とパフォーマンスが向上します。


75-80: キャッシュ実装は良いですが、さらに改善できます

practice_idsメソッドを活用することで、パフォーマンスがさらに向上します。

   def product_for(learning)
-    @products_cache ||= user.products.where(practice_id: completed_practices.pluck(:practice_id))
+    @products_cache ||= user.products.where(practice_id: practice_ids)
                             .includes(:practice, :checks)
                             .index_by(&:practice_id)
     @products_cache[learning.practice_id]
   end

117-126: privateメソッドでもpractice_idsキャッシュを活用してください

両メソッドでcompleted_practices.pluck(:practice_id)を呼び出しています。practice_idsメソッドを使用することで、パフォーマンスが向上します。

   def copy_destinations
-    @copy_destinations ||= Practice.where(source_id: completed_practices.pluck(:practice_id))
+    @copy_destinations ||= Practice.where(source_id: practice_ids)
                                    .pluck(:source_id)
                                    .to_set
   end

   def copy_destination_practices
-    @copy_destination_practices ||= Practice.where(source_id: completed_practices.pluck(:practice_id))
+    @copy_destination_practices ||= Practice.where(source_id: practice_ids)
                                             .includes(:learnings, :products, learnings: :user, products: %i[user checks])
                                             .index_by(&:source_id)
   end
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3a1e62 and 3210ec9.

📒 Files selected for processing (2)
  • app/presenters/practice_progress_presenter.rb (1 hunks)
  • app/views/admin/users/practice_progress/show.html.slim (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/presenters/practice_progress_presenter.rb (1)
app/models/practice.rb (1)
  • learning (125-127)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test
🔇 Additional comments (5)
app/presenters/practice_progress_presenter.rb (2)

13-26: 効率的なeager loadingの実装を評価します

関連データの事前読み込みが適切に実装されており、N+1クエリ問題を回避できています。


103-104: エイリアスの使用が適切です

メソッド名の一貫性を保つためのエイリアス定義は良い実践です。

app/views/admin/users/practice_progress/show.html.slim (3)

1-26: ヘッダー構造が適切に実装されています

管理画面の標準的な構造に従っており、ナビゲーションも適切です。


70-75: 進捗コピー機能の実装が適切です

確認ダイアログによる誤操作防止も実装されており、ユーザビリティに配慮されています。


97-100: 全進捗コピー機能が適切に実装されています

重要な操作であることを示す警告色のボタンと確認ダイアログにより、誤操作を防ぐ設計になっています。

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (12)
app/interactors/copy_practice_progress.rb (1)

37-41: message キーの上書きで最終ステップの文言しか残りません

複数ステップの成功メッセージを残したい場合、merge_result でハッシュをそのまま上書きすると最後に実行したインタラクタの :message だけが残ります。メッセージを蓄積する、もしくは構造化(配列・ハッシュ)して持たせる設計をご検討ください。

-      result.to_h.each { |key, value| context[key] = value }
+      result.to_h.each do |key, value|
+        if key == :message
+          context[:messages] ||= []
+          context[:messages] << value
+        else
+          context[key] = value
+        end
+      end
test/interactors/copy_product_test.rb (1)

62-71: 期待メッセージがプロダクトではなくラーニングを指しています

CopyProduct のテストで "Learning copied successfully (no product to copy)" を期待すると意図が伝わりづらく、実装変更時に誤検知を招きます。プロダクト用のメッセージに合わせて修正してください。

-    assert_equal 'Learning copied successfully (no product to copy)', result.message
+    assert_equal 'No product to copy', result.message
test/interactors/copy_check_test.rb (2)

41-47: メッセージが他インタラクタ用の文言になっています

CopyCheck 単体では 学習・プロダクト のコピーは行わないため、
'Learning, product, and checks copied successfully' は誤解を招きます。
テストも実装も Checks copied successfully 等に合わせましょう。


68-74: 上記と同様にメッセージが不適切です

こちらのケースも正しい文言へ更新してください。

app/models/practice_progress_migrator.rb (1)

40-46: ループ内での Practice.find_by が N+1 の原因に

process_all_practices では完了済み Learning 毎に Practice.find_by(source_id: …) を発行しています。完了済みが多い場合にクエリが急増します。
あらかじめ target practice を一括取得しハッシュで参照するか、Practice.where(source_id: completed_practices.pluck(:practice_id)) でまとめて取得するとパフォーマンスが向上します。

app/interactors/copy_learning.rb (1)

21-25: Interactor の必須キーは DSL で宣言すると簡潔

現在は手動で presence チェックを行っていますが、Interactor には

required_context :user, :from_practice, :to_practice

の DSL があり、重複を避けられます。可読性向上のため検討ください。

app/interactors/copy_product.rb (1)

35-37: メッセージ文言が学習ではなく Product を指すべき

CopyProduct 内で Product が存在しない場合のメッセージが
'Learning copied successfully (no product to copy)' となっており文脈と合いません。誤解を招くため「Product」に修正することを推奨します。

- context.message = 'Learning copied successfully (no product to copy)'
+ context.message = 'No product found, skipping product copy'
test/interactors/copy_practice_progress_test.rb (3)

12-16: fixture クリア処理が広すぎる/重複している可能性
@user.products.destroy_alldependent: :destroy が設定されていれば Check も削除されるはずです。さらに Check.where(checkable_type: 'Product').destroy_all で全ユーザー分の Check を消しており、他テストへの副作用を起こす恐れがあります。テストの isolation はトランザクションで担保されるため、ここではユーザー関連だけを削除する、あるいはそもそも削除処理を省く方が安全です。

-@user.products.destroy_all
-Check.where(checkable_type: 'Product').destroy_all
+# プロダクト削除のみで十分
+@user.products.destroy_all

20-26: status 文字列のハードコーディング
Learning に enum が定義されている場合、'complete' の直接指定ではなく :complete シンボルを使うと将来的なリネーム時に安全です。


35-37: メッセージの完全一致 Assert は brittle
文言が変わるとテストが壊れやすいため、assert_match でキーフレーズのみ確認するか、成功フラグだけを検証する方が保守性が高まります。

-assert_equal 'Learning, product, and checks copied successfully', result.message
+assert_match(/copied successfully/, result.message)
test/interactors/copy_learning_test.rb (2)

11-13: データ削除は最小限に
トランザクションフィクスチャが有効であれば @user.learnings.destroy_all は不要です。速度向上と意図の明確化のため削除を検討してください。


85-87: スタブの副作用を抑えるためスコープを限定
Learning.stub :create! は全テストで有効になります。ブロック内限定とはいえ、ネストされたメソッド呼び出しで思わぬ影響を与える可能性があるため、stub ではなく mocha のような expectation ライブラリで対象メソッドだけを stub する、もしくは CopyLearning クラスに DI する手法がより安全です。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3210ec9 and 3f302e5.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • Gemfile (2 hunks)
  • app/interactors/copy_check.rb (1 hunks)
  • app/interactors/copy_learning.rb (1 hunks)
  • app/interactors/copy_practice_progress.rb (1 hunks)
  • app/interactors/copy_product.rb (1 hunks)
  • app/models/practice_progress_migrator.rb (1 hunks)
  • test/interactors/copy_check_test.rb (1 hunks)
  • test/interactors/copy_learning_test.rb (1 hunks)
  • test/interactors/copy_practice_progress_test.rb (1 hunks)
  • test/interactors/copy_product_test.rb (1 hunks)
  • test/models/practice_progress_migrator_test.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Gemfile
  • test/models/practice_progress_migrator_test.rb
🧰 Additional context used
🧬 Code Graph Analysis (1)
app/interactors/copy_learning.rb (4)
app/interactors/copy_product.rb (3)
  • include (3-70)
  • call (6-17)
  • validate_inputs (21-25)
app/interactors/copy_practice_progress.rb (2)
  • include (3-45)
  • call (6-18)
app/interactors/copy_check.rb (2)
  • include (3-86)
  • call (6-12)
app/models/practice.rb (1)
  • status (93-103)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test
🔇 Additional comments (4)
app/interactors/copy_learning.rb (1)

55-61: コピーする属性が status のみで十分か再確認を

Learning には started_at, completed_at, remarks など他メタ情報が存在する場合があります。進捗コピーの目的が「完了状態の再現」であればタイムスタンプも合わせて複製しないと履歴が失われる恐れがあります。要件を確認のうえ必要であれば追加してください。

app/interactors/copy_check.rb (1)

72-83: コピー時にチェックの付帯情報が落ちる可能性

Check.create!usercheckable しかコピーしていません。
モデルに comment, created_at, updated_at など追加属性が存在する場合、監査用途で困る可能性があります。必要に応じて属性コピーをご検討ください。

test/interactors/copy_practice_progress_test.rb (1)

111-120: パラメータ不足テストの整合性を要確認
user だけ nil を渡しているのに、期待エラーメッセージでは user, from_practice, to_practice すべて欠如としています。実装側と食い違っていないか確認してください。

test/interactors/copy_learning_test.rb (1)

70-78: エラーメッセージの期待値が実態と合致しているか確認
ここでも user, from_practice, to_practice すべて不足というメッセージを期待していますが、user のみ nil です。実装の仕様を再確認し、テストを合わせてください。

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
test/interactors/copy_check_test.rb (1)

12-15: destroy_all の多用はテストの速度と副作用を悪化させます
destroy_all は各レコードのコールバックを実行するため遅く、他テストの fixture に副作用を与える可能性があります。トランザクションで巻かれているテストなら delete_all で十分か、もしくは対象レコードを個別に作成する方式へ変更するとテスト実行時間の短縮と安全性の両立が期待できます。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86a407c and 25f60b5.

📒 Files selected for processing (3)
  • app/interactors/copy_check.rb (1 hunks)
  • app/models/practice_progress_migrator.rb (1 hunks)
  • test/interactors/copy_check_test.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/models/practice_progress_migrator.rb
  • app/interactors/copy_check.rb
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test
🔇 Additional comments (3)
test/interactors/copy_check_test.rb (3)

40-44: 期待メッセージが CopyCheck の責務と合っているか確認してください
assert_equal 'Learning, product, and checks copied successfully', result.message としていますが、CopyCheck は checks のコピーのみを担当するクラスのはずです。上位 Interactor のメッセージを流用していないかご確認ください。責務に合った文言、あるいは assert_match に切り替えることで将来の変更に強くなります。


89-98: 必須パラメータ欠如時に success? を期待する設計を再確認
original_productnil の場合でも result.success? を想定していますが、呼び出し側でバグを見落とす恐れがあります。エラー扱いにすべきか、設計意図を再確認のうえテスト期待値の修正を検討してください。


35-38: Interactor へ Context オブジェクトを渡さない形に修正された点を確認
ハッシュ引数で CopyCheck.call を呼んでおり、過去の指摘事項が解消されています。👍

@komagata komagata requested a review from Copilot June 15, 2025 20:06

This comment was marked as resolved.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
app/presenters/practice_progress_presenter.rb (2)

26-30: includes の指定が冗長で意図が読み取りにくい

includes(%i[learnings products], learnings: :user, products: %i[user checks])
配列とハッシュを併用して同じ関連 (learnings, products) を重複指定しています。
動作に支障はありませんが、可読性が下がりレビューコストが増えます。

-            .includes(%i[learnings products], learnings: :user, products: %i[user checks])
+            .includes(
+              learnings: :user,
+              products:  %i[user checks]
+            )

80-87: キャッシュキーに practice.id だけを用いると衝突の危険性

@copied_practices_cache[practice.id] は course が異なる複数の Presenter
インスタンスを同じユーザで生成した場合に衝突する可能性があります。
course もキーに含めれば安全です。

-    @copied_practices_cache[practice.id] ||= practice.copied_practices
+    cache_key = "#{@course.id}-#{practice.id}"
+    @copied_practices_cache[cache_key] ||= practice.copied_practices
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25f60b5 and 0b6aac3.

📒 Files selected for processing (3)
  • app/interactors/copy_check.rb (1 hunks)
  • app/presenters/practice_progress_presenter.rb (1 hunks)
  • test/interactors/copy_check_test.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/interactors/copy_check.rb
  • test/interactors/copy_check_test.rb
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test

@komagata komagata force-pushed the feature/copy-done-practice branch from 0b6aac3 to a671168 Compare June 15, 2025 20:42
komagata added 8 commits June 16, 2025 06:44
- PracticeProgressMigratorのリファクタリング用にinteractor gemを導入
- 単一責任の原則に従った設計パターンの実装準備
- CopyLearning: Learning単体のコピー処理
- CopyProduct: Product単体のコピー処理
- CopyCheck: Check単体のコピー処理(N+1クエリ最適化済み)
- CopyPracticeProgress: 全体を統括するトランザクション管理
- 各Interactorの包括的なテストを実装
- 単一責任の原則に従った設計
- Interactorパターンを使用したシンプルな設計
- 改善されたエラーハンドリング(RuntimeError回避)
- migrate_allでの一括処理機能
- 包括的なテストカバレッジ
- クエリ最適化済みのPracticeProgressPresenter
- completed_practice_idsのインスタンス変数キャッシュでクエリ削減
- practice_status_forのN+1クエリ問題修正
- migration_candidatesのサブクエリ最適化
- CompletedLearningsQueryで責務分離
- practice_progress_controller: 個別ユーザーの進捗表示
- practice_progress_batches_controller: 一括マイグレーション機能
- 管理画面用のビューテンプレート
- システムテストで動作確認
- Course: copied_practicesの関連付け
- Practice: copied_practicesとsource関連付け
- User: 進捗管理に必要な関連付けの拡張
- 管理画面のルーティング追加
- アプリケーション設定の調整
- 語尾変化ルールの追加
- コード生成品質向上のためのガイドライン設定
@komagata komagata force-pushed the feature/copy-done-practice branch from e0741a0 to 8989e11 Compare June 15, 2025 21:45
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (13)
app/models/course.rb (2)

4-6: 定数の二重管理を排除しましょう
RAILS_COURSEDEFAULT_COURSE が同じ値を保持しており冗長です。どちらか一方に統一すれば保守コストを削減できます。


19-21: 存在しない場合は例外で検知したい
find_by は見つからないと nil を返します。想定外の nil による後続エラーを防ぐため find_by! へ変更し、早期に不整合を検知することを推奨します。

app/controllers/admin/users/practice_progress_batches_controller.rb (2)

6-14: 大量データは非同期ジョブで処理を
migrate_all はユーザーの完了プラクティスを全件コピーします。件数が多い場合はブラウザタイムアウトや DB ロックが懸念されるため、ActiveJob 等でバックグラウンドジョブ化し、完了時に通知する構成を検討してください。


18-20: 想定外例外のフォールバックが欲しい
migrate_all で rescue されない例外が発生すると 500 になります。ここで rescue StandardError => e を追加し、エラーをログ出力した上で alert 返却する保険を置くと運用時の調査が容易です。

app/queries/completed_learnings_query.rb (1)

16-19: coursenil のケースを明示的に防ぐ
@course が渡されない場合は全件ヒットしないだけで静かに失敗します。raise ArgumentError, 'course is required' 等でガードを追加するとバグが埋もれにくくなります。

test/interactors/copy_learning_test.rb (2)

15-37: assert_difference で件数変化を明確に
コピーで Learning が 1 件増えることを assert_difference 'Learning.count', 1 do … end で検証するとテスト意図が読み取りやすくなります。


81-88: 例外スタブは expects を使うと厳密
Learning.stub で例外を投げていますが、Learning.expects(:create!).raises(...) を使うと「メソッドが呼ばれた」ことも同時に検証でき、テストが堅牢になります。

test/interactors/copy_practice_progress_test.rb (1)

35-37: メッセージの完全一致アサートは brittle です

CopyPracticeProgress 内で最後に実行されたサブインタラクタにより context.message が上書きされるため、実装の微修正だけでテストが壊れる恐れがあります。
成功可否だけに絞るか、assert_match で部分一致に留めるほうが保守しやすくなります。

app/interactors/copy_practice_progress.rb (2)

7-14: Interactor::Failure を rescue していない点に注意

merge_result 内で context.fail! が呼ばれると Interactor::Failure が発生しますが、本クラスでは rescue していません。
Interactor 自体が失敗時に例外を握り潰して success? == false のコンテキストを返す仕様なので問題は出ませんが、救済用に ActiveRecord だけを rescue している現状はややアンバランスです。
rescue StandardError => e でまとめるか、コメントで意図を明示すると読み手の理解が早まります。


33-40: merge_result が message を上書きしてしまいます

3 つのサブインタラクタのうち最後(CopyCheck)の message が必ず残るため、途中で発生した「スキップしました」等の情報が欠落します。
複数メッセージを配列で蓄積するか、呼び出し側で使わないと割り切って message をクリアする、などポリシーを決めておくと後々混乱しません。

test/interactors/copy_product_test.rb (1)

34-39: DB 変化を assert_difference で検証すると明確になります

生成された Product の存在確認だけでなく、件数増加を assert_difference('Product.count', 1) でアサートすると意図がより伝わりやすくなります。

app/controllers/admin/users/practice_progress_controller.rb (2)

15-18: exists?find の二重クエリを回避できます

Practice.exists? で存在確認後、migrator.migrate 内で再度 Practice.find しているため同じレコードを 2 回検索しています。
コントローラ側で practice = Practice.find_by(id: practice_id) を取得し、そのまま migrator に渡す形に変えると 1 クエリで済みます。


35-37: Strong Parameters の require を使うと意図が明確

params.require(:practice_id).permit!

とすることで必須パラメータであることが宣言的に示せます。現在の params.permit(:practice_id) では欠落を見逃す可能性があります。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0741a0 and 8989e11.

⛔ Files ignored due to path filters (1)
  • Gemfile.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • .github/copilot/guidelines.yml (1 hunks)
  • Gemfile (2 hunks)
  • app/controllers/admin/users/practice_progress_batches_controller.rb (1 hunks)
  • app/controllers/admin/users/practice_progress_controller.rb (1 hunks)
  • app/interactors/copy_check.rb (1 hunks)
  • app/interactors/copy_learning.rb (1 hunks)
  • app/interactors/copy_practice_progress.rb (1 hunks)
  • app/interactors/copy_product.rb (1 hunks)
  • app/models/course.rb (2 hunks)
  • app/models/practice.rb (1 hunks)
  • app/models/practice_progress_migrator.rb (1 hunks)
  • app/models/user.rb (1 hunks)
  • app/presenters/practice_progress_presenter.rb (1 hunks)
  • app/queries/completed_learnings_query.rb (1 hunks)
  • app/views/admin/users/practice_progress/show.html.slim (1 hunks)
  • config/application.rb (1 hunks)
  • config/initializers/inflections.rb (1 hunks)
  • config/routes/admin.rb (1 hunks)
  • test/interactors/copy_check_test.rb (1 hunks)
  • test/interactors/copy_learning_test.rb (1 hunks)
  • test/interactors/copy_practice_progress_test.rb (1 hunks)
  • test/interactors/copy_product_test.rb (1 hunks)
  • test/models/practice_progress_migrator_test.rb (1 hunks)
  • test/system/admin/users/practice_progress_test.rb (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • config/initializers/inflections.rb
  • .github/copilot/guidelines.yml
  • config/application.rb
  • app/models/user.rb
  • Gemfile
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/interactors/copy_check_test.rb
  • app/models/practice_progress_migrator.rb
  • app/interactors/copy_check.rb
  • app/presenters/practice_progress_presenter.rb
🧰 Additional context used
🧬 Code Graph Analysis (5)
app/queries/completed_learnings_query.rb (1)
app/presenters/practice_progress_presenter.rb (1)
  • initialize (6-9)
app/controllers/admin/users/practice_progress_batches_controller.rb (2)
app/controllers/admin/users/practice_progress_controller.rb (3)
  • before_action (3-38)
  • create (12-27)
  • set_user (31-33)
app/models/practice_progress_migrator.rb (1)
  • migrate_all (23-35)
app/interactors/copy_product.rb (3)
app/interactors/copy_check.rb (2)
  • include (3-93)
  • call (6-15)
app/interactors/copy_learning.rb (3)
  • include (3-65)
  • call (6-17)
  • validate_inputs (21-25)
app/interactors/copy_practice_progress.rb (2)
  • include (3-41)
  • call (6-14)
app/interactors/copy_practice_progress.rb (3)
app/interactors/copy_check.rb (2)
  • include (3-93)
  • call (6-15)
app/interactors/copy_learning.rb (2)
  • include (3-65)
  • call (6-17)
app/interactors/copy_product.rb (2)
  • include (3-70)
  • call (6-17)
app/controllers/admin/users/practice_progress_controller.rb (2)
app/controllers/admin/users/practice_progress_batches_controller.rb (3)
  • before_action (3-21)
  • create (6-14)
  • set_user (18-20)
app/models/practice_progress_migrator.rb (1)
  • migrate (8-21)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test
🔇 Additional comments (8)
config/routes/admin.rb (1)

10-11: ルーティング追加は問題なし
RESTful に整理されており既存ヘルパと衝突しません。

app/views/admin/users/practice_progress/show.html.slim (3)

70-76: I18n に切り出してください

confirm メッセージ '進捗をコピーしますか?' がハードコードされています。
t('.confirm_copy_progress') など I18n に乗せると多言語対応・テスト容易性が向上します。
[ suggest_optional_refactor ]


98-100: 全件コピーの confirm も同様に I18n 化を

こちらも '全ての進捗をコピーしますか?' が直書きです。併せて locale ファイルへ。
[ suggest_optional_refactor ]


63-67: 表示用固定文字列の散在を整理

セル内の 'なし', '未着手' が複数箇所で直書きされています。
t('common.none'), t('common.not_started') など共通キーで一元管理すると保守コストが下がります。
[ suggest_nitpick ]

Also applies to: 77-83, 84-96

app/interactors/copy_learning.rb (1)

38-43: 同時実行で重複レコードが発生する恐れ

existing_learning を確認した後に Learning.create! していますが、並行リクエストが入ると UNIQUE 制約違反になる可能性があります。
Learning.find_or_create_by!(user:, practice:) もしくは DB 制約を rescue してスキップする処理を追加すると安全です。
[ suggest_essential_refactor ]

Also applies to: 54-61

app/interactors/copy_product.rb (1)

46-55: 既存 Product を上書きすべき要件と実装が食い違っています

existing_product_found? でコピーをスキップしていますが、システムテストでは既存 Product の body 更新を期待しています。
要件が「上書き」なら下記のように更新してから return してください。

-      context.copied_product = context.existing_product
-      context.message = 'Product already exists, skipping copy'
+      context.existing_product.update!(body: context.original_product.body,
+                                      wip:  context.original_product.wip)
+      context.copied_product = context.existing_product
+      context.message = 'Product updated successfully'

[ raise_critical_issue ]

test/system/admin/users/practice_progress_test.rb (1)

417-485: テストが落ちる見込みがあります

既存 Product の body を 'Updated submission' へ更新することを期待していますが、現行 CopyProduct は更新ロジックを持ちません。
Interactor を修正するか、期待値を見直してください。
[ request_verification ]

test/models/practice_progress_migrator_test.rb (1)

122-129: テスト間で期待動作が矛盾しています

ここでは「既存 Product を保持する」ことをテストしていますが、system テストでは「上書きする」挙動を期待しています。
仕様を明確化し、両テストを一致させてください。
[ request_verification ]

Comment on lines +54 to +57
# Practice copy relationships
has_many :copied_practices, class_name: 'Practice', foreign_key: 'source_id', dependent: :nullify, inverse_of: :source_practice
belongs_to :source_practice, class_name: 'Practice', foreign_key: 'source_id', optional: true, inverse_of: :copied_practices

Copy link
Copy Markdown

@coderabbitai coderabbitai bot Jun 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

自己参照関連のガードを追加してください

source_practice が自身を指し示すループや多段コピーチェーンを防ぐバリデーションが無いので、不正データが混入すると再帰クエリで無限ループを招く恐れがあります。
validate :source_id_cannot_be_self などで自己参照を弾くことを推奨します。併せて source_id にはインデックスを張っておくとコピー検索時のパフォーマンスも向上します。

🤖 Prompt for AI Agents
In app/models/practice.rb around lines 54 to 57, add a validation method named
source_id_cannot_be_self to prevent the source_practice from referencing itself,
which can cause infinite recursion in queries. Implement this validation to
check if source_id equals the record's own id and add an error if so. Also,
ensure that a database index is created on the source_id column to improve
performance when querying copied practices.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CodeRabbit 指摘点を修正しました。

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@komagata ありがとうございます!

コードを確認したところ、指摘した両方の点が適切に修正されていることが確認できました:

自己参照防止バリデーション: validate :source_id_cannot_be_self が追加され、source_id が自身のIDと同じ場合にエラーを追加する実装になっています

パフォーマンス改善: source_id カラムにインデックスが追加されており、コピー検索時のパフォーマンスが向上します

バリデーションロジックも適切にガードクローズが設定されており、nil 値の処理も考慮されています。これで無限ループのリスクが回避され、安全に Practice のコピー関係を扱えるようになりました。

- 既存データがある場合はスキップする仕様に合わせてテストを更新
- test名を「updates」から「skips」に変更
- 期待値を「データが更新される」から「既存データが保持される」に修正
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
test/system/admin/users/practice_progress_test.rb (6)

15-16: 重複回避のための destroy_all はコスト大 & 可読性低下

毎回 Learning.where(...).destroy_all でレコードを全消ししていますが、対象が 1 件であることが分かっているなら delete_by で十分です。また、find_or_create_by で済むシナリオなら削除自体を省略出来ます。
細かいですが大量テスト時の実行時間短縮と意図の明確化に繋がります。


98-106: Time.current.to_i での一意性保証はレースに弱い

timestamp = Time.current.to_i は 1 秒内に複数テストが走ると衝突の可能性があります。SecureRandom.uuid など衝突しない値を使うか、Factory の sequence を利用するとより堅牢です。

-require 'securerandom'
-timestamp = Time.current.to_i
+require 'securerandom'
+timestamp = SecureRandom.uuid

260-304: CSS セレクタの揺れを統一すると読みやすさが向上します

within 'tbody tr'within 'tr' の 2 パターンが混在しています。DOM 構造が変わった際にテストが部分的に落ちる恐れがあるので、共通 helper を作成して行セレクト方式を一本化することを推奨します。


392-401: 遷移先のパスも合わせて検証すると回帰に強くなります

フラッシュメッセージだけでなく、assert_current_path でリダイレクト先が意図通り /admin/users/:id/practice_progress に戻っているか確認すると UI 変更による誤動作を早期検知できます。


478-485: 「更新されないこと」を検証するなら件数チェックも追加したい

既存レコードが維持されたことを body 文字列で確認していますが、assert_no_difference 'Product.where(...).count' も加えると「新規に複製されていない」ことまで保証できます。


565-587: 一括コピー後の副次効果をより網羅的に確認できます

学習・提出物は確認していますが、Check レコードもコピー対象であれば同様に件数・内容を assert するとテストの網羅性が上がります。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8989e11 and e8f3993.

📒 Files selected for processing (1)
  • test/system/admin/users/practice_progress_test.rb (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
test/system/admin/users/practice_progress_test.rb (1)
app/models/practice.rb (2)
  • product (137-139)
  • status (93-103)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test

komagata added 4 commits June 16, 2025 14:30
- PracticeProgressMigratorのmigrate_allメソッドでトランザクション処理を改善
- PracticeProgressBatchesControllerでInteractor結果のハンドリング修正
- CopyProductでメッセージを簡潔化
- CopyCheckでメッセージ設定ロジックを簡素化
- テストファイルでデータクリーンアップ処理を改善
- CompletedLearningsQueryクラスを追加
- 実装で設定される「Product not found – skipping copy」に合わせて
  テストの期待値を「No product to copy」から修正
- 実装とテストの整合性を確保
@komagata komagata self-assigned this Jun 18, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
test/models/practice_test.rb (2)

59-65: ハードコードされたエラーメッセージは将来的に壊れやすいです
'cannot reference itself' を直接比較すると、i18n で文言が変更された場合にテストが落ちます。
エラー種別シンボルを用いた errors.added? での検証に差し替えると堅牢になります。

-    assert_includes practice.errors[:source_id], 'cannot reference itself'
+    # バリデーション側で `errors.add(:source_id, :self_reference)` としている前提
+    assert practice.errors.added?(:source_id, :self_reference)

67-80: 失敗時のデバッグ容易性を高めるアサーションメッセージの追加を検討してください
assert practice.valid? だけだと失敗時に何が原因か分かりにくいため、practice.errors.full_messages を渡すと調査が速くなります。

-    assert practice.valid?
+    assert practice.valid?, practice.errors.full_messages
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73f9e2b and 3287d09.

📒 Files selected for processing (5)
  • app/models/practice.rb (2 hunks)
  • db/migrate/20250618142946_add_index_on_source_id_to_practices.rb (1 hunks)
  • db/schema.rb (3 hunks)
  • test/models/practice_test.rb (1 hunks)
  • test/system/admin/users/practice_progress_test.rb (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • db/migrate/20250618142946_add_index_on_source_id_to_practices.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/system/admin/users/practice_progress_test.rb
  • app/models/practice.rb
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test
🔇 Additional comments (1)
db/schema.rb (1)

902-910: 以下のスクリプトで db/schema.rb 内の users テーブルに対する全ての add_index 定義を再確認してください。

#!/bin/bash
# usersテーブルに関する全ての add_index 定義を検索
rg -n 'add_index.*"users"' db/schema.rb

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
db/migrate/20250618143741_add_unique_index_to_users_email_and_login_name.rb (1)

3-4: 既存インデックス・重複レコードの存在確認を

login_name 列には既にユニークインデックス index_users_on_login_name が存在している可能性があります。重複インデックスを作成しようとするとエラーになりますので、if_not_exists: true で安全にしておくと安心です。重複レコードの有無もご確認ください。

-    add_index :users, :login_name, unique: true
+    add_index :users, :login_name, unique: true, if_not_exists: true
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3287d09 and 8255e40.

📒 Files selected for processing (2)
  • db/migrate/20250618143741_add_unique_index_to_users_email_and_login_name.rb (1 hunks)
  • db/schema.rb (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • db/schema.rb
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test
🔇 Additional comments (1)
db/migrate/20250618143741_add_unique_index_to_users_email_and_login_name.rb (1)

3-4: まず、PostgreSQL を使っているか確認するために以下のスクリプトを実行してください:

#!/bin/bash
# config/database.yml に PostgreSQL アダプタが設定されているか確認
grep -R "adapter:\s*postgresql" -n config/database.yml || echo "No PostgreSQL adapter found in config/database.yml"

# Gemfile に pg gem が含まれているか確認
grep -R "gem ['\"]pg['\"]" -n Gemfile || echo "No pg gem found in Gemfile"

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8255e40 and 3e55b32.

📒 Files selected for processing (4)
  • app/queries/completed_learnings_query.rb (1 hunks)
  • db/migrate/20250618144325_add_foreign_key_to_practices_source_id.rb (1 hunks)
  • db/schema.rb (4 hunks)
  • test/models/practice_test.rb (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • test/models/practice_test.rb
  • app/queries/completed_learnings_query.rb
  • db/schema.rb
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: build_and_test

Comment on lines +2 to +4
def change
add_foreign_key :practices, :practices, column: :source_id
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

❓ Verification inconclusive

外部キーの即時検証によるテーブルロックを回避しましょう
add_foreign_key はデフォルトで制約を同時に検証するため、大きいテーブルでは書き込みをブロックする時間が長くなる恐れがあります。validate: false を付けて制約だけ先に追加し、別マイグレーションで validate_foreign_key :practices, :source_id を行うとロック時間を短縮できます。既存データに不整合がないことも事前に確認してください。

-    add_foreign_key :practices, :practices, column: :source_id
+    # 検証は後段で実施してロック時間を最小化
+    add_foreign_key :practices, :practices, column: :source_id, validate: false

(必要に応じて on_delete: :nullify 等の振る舞いも検討してください)


外部キーの即時検証によるテーブルロックを回避しましょう
add_foreign_key はデフォルトで制約を同時に検証するため、大きいテーブルでは書き込みをブロックする時間が長くなる恐れがあります。validate: false を付けて制約だけ先に追加し、別マイグレーションで validate_foreign_key :practices, :source_id を行うとロック時間を短縮できます。既存データに不整合がないことも事前に確認してください。

-    add_foreign_key :practices, :practices, column: :source_id
+    # 検証は後段で実施してロック時間を最小化
+    add_foreign_key :practices, :practices, column: :source_id, validate: false

(必要に応じて on_delete: :nullify 等の振る舞いも検討してください)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def change
add_foreign_key :practices, :practices, column: :source_id
end
def change
# 検証は後段で実施してロック時間を最小化
add_foreign_key :practices, :practices, column: :source_id, validate: false
end
🤖 Prompt for AI Agents
In db/migrate/20250618144325_add_foreign_key_to_practices_source_id.rb around
lines 2 to 4, the add_foreign_key call immediately validates the foreign key
constraint, which can cause long table locks on large tables. To fix this, add
the option validate: false to the add_foreign_key method to defer validation,
then create a separate migration to run validate_foreign_key :practices,
:source_id. Also, verify beforehand that existing data has no inconsistencies.
Optionally, consider adding on_delete behavior like on_delete: :nullify if
appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants