Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aa7d790
interactor gemを追加
komagata Jun 15, 2025
214df18
Interactorパターンでコピー機能を実装
komagata Jun 15, 2025
819692a
PracticeProgressMigratorを実装
komagata Jun 15, 2025
30be391
PracticeProgressPresenterとCompletedLearningsQueryを実装
komagata Jun 15, 2025
da96ee7
管理画面のプラクティス進捗管理機能を実装
komagata Jun 15, 2025
5b5f3cd
モデルの関連付けを追加
komagata Jun 15, 2025
6f7955f
設定ファイルを更新
komagata Jun 15, 2025
8989e11
GitHub Copilotガイドラインを追加
komagata Jun 15, 2025
e8f3993
システムテストを実装仕様に合わせて修正
komagata Jun 16, 2025
a112dd6
コードの改善とテスト修正
komagata Jun 16, 2025
146f1af
CopyProductテストのメッセージ期待値を修正
komagata Jun 16, 2025
8158737
コントローラーのエラーハンドリングを修正:migrate_allのboolean戻り値に対応
komagata Jun 16, 2025
a8dc220
テストを実装に合わせて修正:メッセージ期待値とInteractorコンテキスト対応
komagata Jun 16, 2025
910489b
PracticeProgressMigratorをBoolean戻り値に戻し、コントローラーを対応
komagata Jun 16, 2025
72895bf
CopyCheckテストのメッセージ期待値を実装に合わせて修正
komagata Jun 16, 2025
73f9e2b
システムテストでfixtureデータ直接修正を回避:専用テストデータを作成してテスト間干渉を防止
komagata Jun 16, 2025
57678f7
RuboCop修正:末尾の空白を削除
komagata Jun 18, 2025
3df49e3
Practiceモデルにsource_id自己参照防止バリデーションとインデックスを追加
komagata Jun 18, 2025
3287d09
RuboCop修正:不要なprivate修飾子削除とレイアウト調整
komagata Jun 18, 2025
8255e40
Userモデルのemailとlogin_nameにユニークインデックスを追加:RuboCop警告解消
komagata Jun 18, 2025
43575ca
practicesテーブルのsource_idに外部キー制約を追加:参照整合性を強化
komagata Jun 18, 2025
00be220
RuboCop修正:末尾空白削除と数値リテラル書式修正
komagata Jun 18, 2025
3e55b32
CompletedLearningsQueryの修正:courses_categoriesでcourse_idを使用
komagata Jun 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/copilot/guidelines.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# GitHub Copilot Guidelines

# Pull Request Review Guidelines
review:
language: "ja"
description: "Pull Requestのレビューは日本語で行うこと"

# Code Review Standards
standards:
- "コードレビューでは日本語でコメントを記載する"
- "技術的な議論も日本語で行う"
- "レビューコメントは建設的で丁寧な表現を心がける"
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ gem 'good_job', '~> 3.14', github: 'komagata/good_job'
gem 'google-cloud-storage', '~> 1.25', require: false
gem 'holiday_jp'
gem 'icalendar', '~> 2.8'
gem 'interactor', '~> 3.0'
gem 'jp_prefecture', '~> 1.1'
gem 'jquery-rails'
gem 'kaminari'
Expand All @@ -62,6 +63,7 @@ gem 'rack-cors', require: 'rack/cors'
gem 'rack-user_agent'
gem 'rails_autolink'
gem 'rails-i18n', '~> 6.0.0'
gem 'rails-patterns', '~> 0.2'
gem 'ransack', '3.1.0'
gem 'react-rails'
gem 'recaptcha', '~> 5.12'
Expand Down
23 changes: 23 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ GEM
any_login (1.7.0)
rails (>= 6.1)
ast (2.4.3)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
Expand Down Expand Up @@ -137,6 +141,8 @@ GEM
cocooned (2.3.0)
rails (>= 6.1, <= 8.0)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
concurrent-ruby (1.3.4)
connection_pool (2.5.3)
countries (7.1.1)
Expand All @@ -152,6 +158,8 @@ GEM
railties (>= 6.1)
date (3.4.1)
declarative (0.0.20)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
diffy (3.4.3)
digest-crc (0.7.0)
rake (>= 12.0.0, < 14.0.0)
Expand Down Expand Up @@ -243,9 +251,11 @@ GEM
logger
ostruct
ice_cube (0.17.0)
ice_nine (0.11.2)
image_processing (1.14.0)
mini_magick (>= 4.9.5, < 6)
ruby-vips (>= 2.0.17, < 3)
interactor (3.1.2)
jbuilder (2.13.0)
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
Expand Down Expand Up @@ -446,6 +456,11 @@ GEM
rails-i18n (6.0.0)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 7)
rails-patterns (0.11.0)
actionpack (>= 4.2.6)
activerecord (>= 4.2.6)
ruby2_keywords
virtus
rails_autolink (1.1.8)
actionview (> 3.1)
activesupport (> 3.1)
Expand Down Expand Up @@ -524,6 +539,7 @@ GEM
ruby-vips (2.2.3)
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5)
rubyzip (2.4.1)
selenium-webdriver (4.17.0)
base64 (~> 0.2)
Expand Down Expand Up @@ -573,6 +589,7 @@ GEM
stripe (15.2.0)
temple (0.10.3)
thor (1.3.2)
thread_safe (0.3.6)
tilt (2.6.0)
timeout (0.4.3)
traceroute (0.8.1)
Expand All @@ -594,6 +611,10 @@ GEM
method_source (~> 1.0)
view_source_map (0.3.0)
rails (>= 5)
virtus (2.0.0)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down Expand Up @@ -660,6 +681,7 @@ DEPENDENCIES
holiday_jp
icalendar (~> 2.8)
image_processing (~> 1.12)
interactor (~> 3.0)
jbuilder (~> 2.7)
jp_prefecture (~> 1.1)
jquery-rails
Expand Down Expand Up @@ -696,6 +718,7 @@ DEPENDENCIES
rack-user_agent
rails (~> 6.1.7.10)
rails-i18n (~> 6.0.0)
rails-patterns (~> 0.2)
rails_autolink
ransack (= 3.1.0)
react-rails
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

class Admin::Users::PracticeProgressBatchesController < AdminController
before_action :set_user

def create
migrator = PracticeProgressMigrator.new(@user)

result = migrator.migrate_all
if result
redirect_to admin_user_practice_progress_path(@user), notice: '全ての進捗をコピーしました。'
else
redirect_to admin_user_practice_progress_path(@user), alert: '進捗のコピーに失敗しました。'
end
end

private

def set_user
@user = User.find(params[:user_id])
end
end
38 changes: 38 additions & 0 deletions app/controllers/admin/users/practice_progress_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

class Admin::Users::PracticeProgressController < AdminController
before_action :set_user

def show
@presenter = PracticeProgressPresenter.new(@user)
@current_course = @user.course
@rails_course = Course.rails_course
end

def create
practice_id = practice_progress_params[:practice_id]

unless practice_id.present? && Practice.exists?(practice_id)
redirect_to admin_user_practice_progress_path(@user), alert: 'プラクティスが見つかりません'
return
end

migrator = PracticeProgressMigrator.new(@user)

if migrator.migrate(practice_id)
redirect_to admin_user_practice_progress_path(@user), notice: '進捗をコピーしました。'
else
redirect_to admin_user_practice_progress_path(@user), alert: 'コピー先のプラクティスが見つかりません。'
end
end

private

def set_user
@user = User.find(params[:user_id])
end

def practice_progress_params
params.permit(:practice_id)
end
end
89 changes: 89 additions & 0 deletions app/interactors/copy_check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# frozen_string_literal: true

class CopyCheck
include Interactor

def call
# Skip if no product was copied but consider it successful
unless context.copied_product && context.original_product
context.message = 'No product available for check copying, skipping'
return
end

find_original_checks
copy_all_checks
end

private

def find_original_checks
context.original_checks = context.original_product.checks.to_a

context.message = if context.original_checks.empty?
'No checks found to copy'
else
"Found #{context.original_checks.size} check(s) to copy"
end
end

def copy_all_checks
return if context.original_checks.empty?

results = process_checks
store_results(results)
update_completion_message(results)
rescue ActiveRecord::RecordInvalid => e
context.fail!(error: "Failed to create check: #{e.message}")
end

def process_checks
copied_count = 0
skipped_count = 0

# Fetch all existing check user IDs for the copied product in one query
existing_user_ids = Check.where(checkable: context.copied_product)
.pluck(:user_id)
.to_set

context.original_checks.each do |original_check|
if copy_check(original_check, existing_user_ids)
copied_count += 1
else
skipped_count += 1
end
end

{ copied: copied_count, skipped: skipped_count }
end

def store_results(results)
context.copied_checks_count = results[:copied]
context.skipped_checks_count = results[:skipped]
end

def update_completion_message(results)
context.message = build_summary_message(results)
end

def products_available?
context.original_product && context.copied_product
end

def build_summary_message(results)
"Copied #{results[:copied]} check(s), skipped #{results[:skipped]} existing check(s)"
end

def copy_check(original_check, existing_user_ids)
# Check if this user already has a check for the copied product
return false if existing_user_ids.include?(original_check.user_id)

Check.create!(
user: original_check.user,
checkable: context.copied_product
)

# Add the new user ID to the set to avoid duplicates in subsequent iterations
existing_user_ids.add(original_check.user_id)
true
end
end
65 changes: 65 additions & 0 deletions app/interactors/copy_learning.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

class CopyLearning
include Interactor

def call
validate_inputs
return if context.failure?

find_original_learning
return if context.failure?

check_existing_learning
return if existing_learning_found?

create_copied_learning
end

private

def validate_inputs
return if context.user && context.from_practice && context.to_practice

context.fail!(error: 'Missing required parameters: user, from_practice, to_practice')
end

def find_original_learning
context.original_learning = Learning.find_by(
user: context.user,
practice: context.from_practice
)

return if context.original_learning

context.fail!(error: 'Original learning not found')
end

def check_existing_learning
context.existing_learning = Learning.find_by(
user: context.user,
practice: context.to_practice
)
end

def existing_learning_found?
if context.existing_learning
context.message = 'Learning already exists, skipping copy'
true
else
false
end
end

def create_copied_learning
context.copied_learning = Learning.create!(
user: context.user,
practice: context.to_practice,
status: context.original_learning.status
)

context.message = 'Learning copied successfully'
rescue ActiveRecord::RecordInvalid => e
context.fail!(error: "Failed to create learning: #{e.message}")
end
end
41 changes: 41 additions & 0 deletions app/interactors/copy_practice_progress.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

class CopyPracticeProgress
include Interactor

def call
ActiveRecord::Base.transaction do
run_learning_copy
run_product_copy
run_check_copy
end
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotFound => e
context.fail!(error: e.message)
end

private

def run_learning_copy
result = CopyLearning.call(context.to_h)
merge_result(result)
end

def run_product_copy
result = CopyProduct.call(context.to_h)
merge_result(result)
end

def run_check_copy
result = CopyCheck.call(context.to_h)
merge_result(result)
end

def merge_result(result)
if result.success?
# Merge successful result data into context
result.to_h.each { |key, value| context[key] = value }
else
context.fail!(error: result.error)
end
end
end
Loading