대진표 관리 앱에서 두 가지 버그가 동시에 나왔다.

  1. 선수 추가 폼이 작동하지 않는 것처럼 보임 — 추가 버튼을 눌러도 목록이 갱신 안 됨
  2. 자동배정 후 선수를 추가하고 다시 자동배정해도 빈 슬롯이 안 채워짐

겉으로 보면 “선수 생성이 안 된다"인데, 실제로는 두 개의 독립적인 버그가 동시에 나타난 케이스였다.


증상 정리

조작기대실제
선수 추가 폼 제출목록에 즉시 반영아무 변화 없음 (새로고침하면 있음)
자동배정 → 선수 추가 → 자동배정새 선수가 빈 슬롯에 배정“배정할 선수가 없습니다” or 변화 없음
빈 슬롯 표시공란“BYE” 텍스트 노출

버그 1: Turbo Stream 응답 누락

원인

Rails 8 + Turbo 환경에서 form_with는 기본적으로 turbo_stream 포맷으로 제출한다. 컨트롤러의 create 액션이 이렇게 되어 있었다:

def create
  @player = @tournament.players.build(player_params)

  if @player.save
    respond_to do |format|
      format.html { redirect_to players_path, notice: "Added." }
      format.json { render json: @player, status: :created }
      format.turbo_stream  # ← 블록 없음, 템플릿도 없음
    end
  end
end

format.turbo_stream이 블록 없이 호출되면 Rails는 create.turbo_stream.erb를 찾는다. 이 파일이 없으면 ActionView::MissingTemplate 에러가 발생한다. 하지만 Turbo가 이 에러를 조용히 삼키기 때문에, 사용자에게는 “버튼을 눌러도 아무 일도 안 일어남"으로 보인다.

같은 문제가 update, destroy, update_status, withdraw 액션에도 있었다. 전부 format.turbo_stream 블록이 비어 있었다.

수정

def create
  @player = @tournament.players.build(player_params)

  if @player.save
    respond_to do |format|
      format.html { redirect_to players_path, notice: "Added." }
      format.json { render json: @player, status: :created }
      format.turbo_stream do
        @player = @tournament.players.build(status: :active)
        prepare_index_state
        flash.now[:notice] = "Player was successfully added."
        render :index  # index.turbo_stream.erb → 전체 목록 교체
      end
    end
  end
end

핵심: turbo_stream 블록 안에서 상태를 리셋하고 render :index로 기존 turbo_stream 템플릿을 재활용.

추가로, turbo_stream 템플릿이 turbo_stream.replace "players-page"를 호출하므로 타겟 ID가 HTML에 존재해야 한다:

<%# index.turbo_stream.erb %>
<%= turbo_stream.replace "players-page" do %>
  <%= render "page" %>
<% end %>

뷰에 id="players-page"가 빠져 있어서 교체 대상을 못 찾는 문제도 함께 수정했다.

교훈

format.turbo_stream 블록 없이 호출하면 반드시 대응하는 .turbo_stream.erb 파일이 있어야 한다. 없으면 조용히 실패한다. 개발 중에는 브라우저 콘솔에서 422 응답을 확인하면 잡을 수 있다.


버그 2: 자동배정이 BYE 슬롯을 재활용하지 않음

배경

8인 대진표에 5명이 참가하면:

  • 슬롯 8개 생성
  • 자동배정 → 5명 배정, 나머지 3개는 bye: true로 마킹

나중에 선수 2명을 추가하고 다시 자동배정을 누르면, 남은 BYE 슬롯에 배정되어야 한다.

원인

자동배정 로직이 이랬다:

def auto_assign
  empty_slots = first_round.bracket_slots
    .where(player_id: nil, bye: false)  # ← BYE 슬롯 제외!
    .to_a

  empty_slots.each do |slot|
    player = unassigned_players.shift
    if player
      slot.update!(player_id: player.id, bye: false)
    else
      slot.update!(player_id: nil, bye: true)  # 남은 슬롯 → BYE
    end
  end
end

첫 자동배정에서 남은 슬롯이 bye: true로 바뀌면, 두 번째 자동배정 때 where(bye: false) 조건에서 완전히 제외된다. 새 선수를 추가해도 배정할 슬롯이 0개다.

반면 랜덤 재배정(reassign_random)은 모든 슬롯을 먼저 bye: false로 리셋한 후 재배정하기 때문에 정상 작동했다.

수정

def auto_assign
  # 빈 슬롯 + BYE 슬롯 모두 활용
  empty_slots = first_round.bracket_slots
    .where(player_id: nil, bye: false).to_a
  bye_slots = first_round.bracket_slots
    .where(bye: true).to_a
  assignable_slots = empty_slots + bye_slots

  assignable_slots.each do |slot|
    player = unassigned_players.shift
    if player
      slot.update!(player_id: player.id, bye: false)
    else
      slot.update!(player_id: nil, bye: true)
    end
  end
end

빈 슬롯을 먼저 채우고, 그래도 선수가 남으면 BYE 슬롯을 되찾아서 배정한다. BYE 슬롯이 bye: false로 바뀌면서 자연스럽게 재활용된다.

검증

# 시나리오: 8슬롯, 처음 4명 → 추가 2명 → 추가 2명

1차 자동배정: 4명 배정, 4 BYE
선수 2명 추가 → 2차 자동배정: 6명 배정, 2 BYE  ✅
선수 2명 추가 → 3차 자동배정: 8명 배정, 0 BYE  ✅

부수 수정: BYE 슬롯 표시

대진표에서 빈 슬롯이 “BYE"라고 표시되는 것도 같이 수정했다. 운영자 입장에서 BYE는 “아직 배정 안 된 빈자리"인데 “BYE"라고 쓰면 “부전승"으로 오해할 수 있다.

<%# Before %>
<span class="text-[11px] italic text-muted">BYE</span>

<%# After — 공란 %>
<span class="text-[11px] text-muted">&nbsp;</span>

데스크탑, 모바일 뷰 모두 동일하게 수정.


helper_method 누락

네비게이션 바에서 managed_tournaments_scope를 호출하는데 이 메서드가 helper_method로 노출되지 않아서 NameError가 발생했다.

# Before
helper_method :app_home_path, :managed_tournament_dashboard_ready?

# After
helper_method :app_home_path, :managed_tournament_dashboard_ready?, :managed_tournaments_scope

컨트롤러에 정의된 private 메서드를 뷰에서 쓰려면 반드시 helper_method로 선언해야 한다. 뷰 파셜에서 직접 호출하면 NameError가 조용히 나온다.


정리

증상실제 원인분류
선수 추가 안 됨turbo_stream 응답 누락 (템플릿 없음)Turbo 설정
자동배정 재실행 안 됨BYE 슬롯이 쿼리에서 제외됨비즈니스 로직
빈 슬롯에 “BYE” 표시하드코딩된 BYE 텍스트UI
네비게이션 에러helper_method 선언 누락Rails 설정

겉으로 하나의 버그(“선수 추가가 안 됨”)로 보이지만, 실제로는 네 개의 독립적인 문제가 동시에 나타난 경우였다. Turbo Stream은 실패해도 사용자에게 에러를 보여주지 않기 때문에, 브라우저 개발자 도구의 Network 탭을 먼저 확인하는 습관이 중요하다.