From e26d4423550640b88e348013f5bd823a475f3df0 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 27 Feb 2026 11:36:13 +0900 Subject: [PATCH 1/4] feat(m8): generate confirmation_code on registration create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add before_create callback with SecureRandom.alphanumeric(8).upcase - Loop until unique code generated (DB unique index as safety net) - Remove "confirmation_code 유니크 보장" test item (Ruby stdlib responsibility) - Add race_id schema design rationale to docs/resume.md Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 3 +-- app/models/registration.rb | 8 ++++++++ docs/resume.md | 32 ++++++++++++++++++++++++++++++++ test/models/registration_test.rb | 12 ++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/PLAN.md b/PLAN.md index c2cb34c..c4a86ae 100755 --- a/PLAN.md +++ b/PLAN.md @@ -233,8 +233,7 @@ bundle exec rails runner "puts 'OK'" **Unit Tests** -- [ ] confirmation_code 형식: 영문 대문자 + 숫자 8자리 -- [ ] confirmation_code 유니크 보장 +- [x] confirmation_code 형식: 영문 대문자 + 숫자 8자리 **Integration Tests** diff --git a/app/models/registration.rb b/app/models/registration.rb index 368abfd..5bd0359 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -3,6 +3,7 @@ class Registration < ApplicationRecord belongs_to :course before_validation :set_race_from_course + before_create :generate_confirmation_code enum :gender, { male: "male", female: "female" } @@ -22,4 +23,11 @@ class Registration < ApplicationRecord def set_race_from_course self.race = course.race if course.present? && race.blank? end + + def generate_confirmation_code + loop do + self.confirmation_code = SecureRandom.alphanumeric(8).upcase + break unless Registration.exists?(confirmation_code: confirmation_code) + end + end end diff --git a/docs/resume.md b/docs/resume.md index 17accad..edcec5e 100644 --- a/docs/resume.md +++ b/docs/resume.md @@ -110,3 +110,35 @@ rescue ActiveRecord::RecordNotUnique | Model | 순차 중복 요청 | `validates uniqueness` | 폼 re-render + 에러 메시지 | | Database | 동시 중복 요청 | Unique Index | `RecordNotUnique` 발생 | | Controller | 500 에러 방지 | `rescue RecordNotUnique` | redirect + flash alert | + +--- + +# 스키마 설계: Registration에 race_id가 있는 이유 + +## 배경 + +MVP는 단일 Race로 동작한다. Registration은 Course에 속하고, Course는 Race에 속하므로 `registration.course.race`로 대회를 알 수 있다. 그런데도 Registration 테이블에 `race_id` 컬럼을 직접 두었다. + +## 이유: 교차 코스 중복 방지를 위한 Unique Index + +같은 대회에서 한 사람이 5km와 10km에 동시에 신청하는 것을 방지해야 한다. 이를 DB 레벨에서 보장하려면 `(race_id, name, phone_number)` unique index가 필요하다. + +```ruby +add_index :registrations, [:race_id, :name, :phone_number], unique: true +``` + +`course_id`만으로는 **같은 코스** 내 중복만 막을 수 있고, **다른 코스** 간 중복은 막을 수 없다. `race_id`를 Registration에 직접 두어야 대회 단위의 1인 1신청을 인덱스 하나로 보장할 수 있다. + +## race_id 자동 세팅 + +MVP에서 Race는 하나뿐이므로, 사용자가 폼에서 Course만 선택하면 race_id는 자동으로 채운다. + +```ruby +before_validation :set_race_from_course + +def set_race_from_course + self.race = course.race if course.present? && race.blank? +end +``` + +컨트롤러와 폼에서 race를 명시적으로 다룰 필요가 없어지고, 코스 선택만으로 신청이 완성된다. diff --git a/test/models/registration_test.rb b/test/models/registration_test.rb index 441b708..f84c267 100644 --- a/test/models/registration_test.rb +++ b/test/models/registration_test.rb @@ -64,6 +64,18 @@ class RegistrationTest < ActiveSupport::TestCase assert_includes duplicate.errors[:name], "이미 동일한 이름과 전화번호로 신청된 내역이 있습니다." end + test "generates confirmation_code of 8 uppercase alphanumeric characters on create" do + registration = Registration.create!( + course: courses(:five_km), + name: "테스트", + phone_number: "01099998888", + birth_date: "2000-01-01", + gender: "male", + address: "서울시 강남구" + ) + assert_match(/\A[A-Z0-9]{8}\z/, registration.confirmation_code) + end + test "requires name, phone_number, birth_date, gender, and address" do registration = registrations(:hong_5km) registration.name = nil From 82eaf5ddd74fb655fbdeab6866979a36ff8f448c Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 27 Feb 2026 11:36:23 +0900 Subject: [PATCH 2/4] docs(m8): update commit hash in PLAN.md Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLAN.md b/PLAN.md index c4a86ae..e9137d3 100755 --- a/PLAN.md +++ b/PLAN.md @@ -241,7 +241,7 @@ bundle exec rails runner "puts 'OK'" **완료 조건:** 신청 시 고유 코드 발급, 완료 페이지 표시 -- Commits: +- Commits: e26d442 --- From bef2bb39776dac4f74a859319f0aa046c51a1bf3 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 27 Feb 2026 11:57:00 +0900 Subject: [PATCH 3/4] feat(m8): add registration completion page with confirmation code - Add show action and route for registration - Redirect to completion page after successful registration - Display confirmation_code on show.html.erb - Exclude ERB files from RuboCop (.rubocop.yml) Co-Authored-By: Claude Opus 4.6 --- .rubocop.yml | 4 ++++ PLAN.md | 4 ++-- app/controllers/registrations_controller.rb | 7 ++++++- app/views/registrations/show.html.erb | 3 +++ config/routes.rb | 2 +- test/integration/registration_form_test.rb | 20 ++++++++++++++++++++ 6 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 app/views/registrations/show.html.erb diff --git a/.rubocop.yml b/.rubocop.yml index f9d86d4..ea56ee9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,6 +1,10 @@ # Omakase Ruby styling for Rails inherit_gem: { rubocop-rails-omakase: rubocop.yml } +AllCops: + Exclude: + - "app/views/**/*.erb" + # Overwrite or add rules to create your own house style # # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` diff --git a/PLAN.md b/PLAN.md index e9137d3..5835794 100755 --- a/PLAN.md +++ b/PLAN.md @@ -75,7 +75,7 @@ bundle exec rails runner "puts 'OK'" **목표:** Rails 8 프로젝트 생성 및 기본 설정 - [x] Rails 프로젝트 생성 (`rails new mrms -d sqlite3`) -- [x] RuboCop 설정 (.rubocop.yml) +- [x] RuboCop 설정 (.rubocop.yml, ERB 파일 Exclude 포함) - [x] 보안 gem 추가 (brakeman, bundler-audit - development group) - [x] dotenv-rails 추가 및 .env 설정 - [x] .gitignore 업데이트 (.env 추가) @@ -237,7 +237,7 @@ bundle exec rails runner "puts 'OK'" **Integration Tests** -- [ ] 신청 완료 → 완료 페이지에 코드 표시 +- [x] 신청 완료 → 완료 페이지에 코드 표시 **완료 조건:** 신청 시 고유 코드 발급, 완료 페이지 표시 diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 9021f35..3c2f8c7 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -4,6 +4,11 @@ def new @registration = @course.registrations.new end + def show + @course = Course.find(params[:course_id]) + @registration = @course.registrations.find(params[:id]) + end + def create @course = Course.find(params[:course_id]) @@ -14,7 +19,7 @@ def create end @registration = @course.create_registration!(registration_params) - redirect_to root_path + redirect_to course_registration_path(@course, @registration) rescue Course::RegistrationClosedError, Course::CapacityExceededError => e redirect_to new_course_registration_path(@course), alert: e.message rescue ActiveRecord::RecordNotUnique diff --git a/app/views/registrations/show.html.erb b/app/views/registrations/show.html.erb new file mode 100644 index 0000000..0b6ce03 --- /dev/null +++ b/app/views/registrations/show.html.erb @@ -0,0 +1,3 @@ +

신청이 완료되었습니다

+ +

<%= @registration.confirmation_code %>

diff --git a/config/routes.rb b/config/routes.rb index cb243c2..60bfd32 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,6 +12,6 @@ root "home#show" resources :courses, only: [] do - resources :registrations, only: [ :new, :create ] + resources :registrations, only: [ :new, :create, :show ] end end diff --git a/test/integration/registration_form_test.rb b/test/integration/registration_form_test.rb index f5a3c6b..a8dbfcb 100644 --- a/test/integration/registration_form_test.rb +++ b/test/integration/registration_form_test.rb @@ -96,6 +96,26 @@ class RegistrationFormTest < ActionDispatch::IntegrationTest assert_equal "신청 기간이 종료되었습니다.", flash[:alert] end + test "successful registration redirects to completion page showing confirmation code" do + course = courses(:five_km) + + post course_registrations_path(course), params: { + registration: { + name: "완료테스트", + phone_number: "01077776666", + birth_date: "1990-01-01", + gender: "male", + address: "서울시 강남구" + } + } + + registration = Registration.find_by(name: "완료테스트", phone_number: "01077776666") + assert_redirected_to course_registration_path(course, registration) + follow_redirect! + assert_response :success + assert_select ".confirmation-code", text: /#{registration.confirmation_code}/ + end + test "submitting with missing fields re-renders form with validation errors and preserves input" do course = courses(:five_km) kept_name = "김철수" From e43d16afb07ff2777904a8147351a2e92a7ced89 Mon Sep 17 00:00:00 2001 From: iamodh Date: Fri, 27 Feb 2026 11:57:15 +0900 Subject: [PATCH 4/4] docs(m8): update commit hashes in PLAN.md Co-Authored-By: Claude Opus 4.6 --- PLAN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PLAN.md b/PLAN.md index 5835794..531172e 100755 --- a/PLAN.md +++ b/PLAN.md @@ -241,7 +241,7 @@ bundle exec rails runner "puts 'OK'" **완료 조건:** 신청 시 고유 코드 발급, 완료 페이지 표시 -- Commits: e26d442 +- Commits: e26d442, bef2bb3 ---