Merge branch 'codex/default-beamer-only' into 'master'

Simplify default beamer handling

See merge request turniere/turniere-backend!43
This commit is contained in:
Daniel Schädler 2026-04-16 11:44:13 +00:00
commit 4f6cf52a84
10 changed files with 77 additions and 26 deletions

View File

@ -20,7 +20,7 @@ class BeamersController < ApplicationController
# GET /tournaments/:tournament_id/beamers/by_name?name=beamername # GET /tournaments/:tournament_id/beamers/by_name?name=beamername
def by_name def by_name
beamer = @tournament.beamers.find_by(name: params[:name]) beamer = @tournament.beamers.find_by(name: params[:name])
beamer ||= @tournament.beamers.default_beamer.first beamer ||= @tournament.beamers.find_by(name: Beamer::DEFAULT_NAME)
if beamer if beamer
render json: beamer render json: beamer
else else
@ -64,6 +64,6 @@ class BeamersController < ApplicationController
end end
def beamer_params def beamer_params
params.permit(:name, :display_state, :is_default, :qr_code_url, :logo_url) params.permit(:name, :display_state, :qr_code_url, :logo_url)
end end
end end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Beamer < ApplicationRecord class Beamer < ApplicationRecord
DEFAULT_NAME = 'default'
DISPLAY_STATES = %w[ DISPLAY_STATES = %w[
matches_all matches_in_progress matches_upcoming matches_not_started matches_all matches_in_progress matches_upcoming matches_not_started
matches_finished groups between_games during_game matches_finished groups between_games during_game
@ -11,7 +12,5 @@ class Beamer < ApplicationRecord
validates :name, presence: true, uniqueness: { scope: :tournament_id } validates :name, presence: true, uniqueness: { scope: :tournament_id }
validates :display_state, presence: true, inclusion: { in: DISPLAY_STATES } validates :display_state, presence: true, inclusion: { in: DISPLAY_STATES }
scope :default_beamer, -> { where(is_default: true) }
delegate :owner, to: :tournament delegate :owner, to: :tournament
end end

View File

@ -16,6 +16,7 @@ class Tournament < ApplicationRecord
alias_attribute :owner, :user alias_attribute :owner, :user
after_initialize :generate_code after_initialize :generate_code
after_create_commit :ensure_default_beamer!
def matches def matches
[stages.map(&:matches), stages.map { |s| s.groups.map(&:matches) }].flatten [stages.map(&:matches), stages.map { |s| s.groups.map(&:matches) }].flatten
@ -50,4 +51,10 @@ class Tournament < ApplicationRecord
errors.add(:playoff_teams_amount, errors.add(:playoff_teams_amount,
'playoff_teams_amount needs to be a positive power of two') 'playoff_teams_amount needs to be a positive power of two')
end end
def ensure_default_beamer!
beamers.find_or_create_by!(name: Beamer::DEFAULT_NAME) do |beamer|
beamer.display_state = 'matches_all'
end
end
end end

View File

@ -1,5 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
class BeamerSerializer < ApplicationSerializer class BeamerSerializer < ApplicationSerializer
attributes :name, :display_state, :is_default, :qr_code_url, :logo_url attributes :name, :display_state, :qr_code_url, :logo_url
end end

View File

@ -0,0 +1,40 @@
# frozen_string_literal: true
class BackfillDefaultBeamersByName < ActiveRecord::Migration[7.0]
DEFAULT_BEAMER_NAME = 'default'
class MigrationTournament < ApplicationRecord
self.table_name = 'tournaments'
end
class MigrationBeamer < ApplicationRecord
self.table_name = 'beamers'
end
def up
MigrationTournament.find_each do |tournament|
beamers = MigrationBeamer.where(tournament_id: tournament.id)
next if beamers.exists?(name: DEFAULT_BEAMER_NAME)
legacy_default = beamers.find_by(is_default: true)
if legacy_default
legacy_default.update_columns(name: DEFAULT_BEAMER_NAME)
else
MigrationBeamer.create!(
tournament_id: tournament.id,
name: DEFAULT_BEAMER_NAME,
display_state: 'matches_all'
)
end
end
end
def down
MigrationBeamer.where(name: DEFAULT_BEAMER_NAME).where.not(tournament_id: nil).find_each do |beamer|
next unless MigrationBeamer.where(tournament_id: beamer.tournament_id).count == 1
beamer.delete
end
end
end

View File

@ -6,8 +6,8 @@ RSpec.describe BeamersController, type: :controller do
before do before do
@tournament = create(:tournament) @tournament = create(:tournament)
@owner = @tournament.owner @owner = @tournament.owner
@default_beamer = @tournament.beamers.find_by!(name: Beamer::DEFAULT_NAME)
@beamer = create(:beamer, tournament: @tournament) @beamer = create(:beamer, tournament: @tournament)
@default_beamer = create(:default_beamer, tournament: @tournament)
end end
describe 'GET #index' do describe 'GET #index' do
@ -47,18 +47,18 @@ RSpec.describe BeamersController, type: :controller do
expect(response).to be_successful expect(response).to be_successful
body = deserialize_response response body = deserialize_response response
expect(body[:name]).to eq(@default_beamer.name) expect(body[:name]).to eq(@default_beamer.name)
expect(body[:is_default]).to be true
end end
it 'falls back to default beamer when name param is missing' do it 'falls back to default beamer when name param is missing' do
get :by_name, params: { tournament_id: @tournament.to_param } get :by_name, params: { tournament_id: @tournament.to_param }
expect(response).to be_successful expect(response).to be_successful
body = deserialize_response response body = deserialize_response response
expect(body[:is_default]).to be true expect(body[:name]).to eq(Beamer::DEFAULT_NAME)
end end
it 'returns 404 when no beamers exist at all' do it 'returns 404 when all beamers were deleted' do
empty_tournament = create(:tournament) empty_tournament = create(:tournament)
empty_tournament.beamers.destroy_all
get :by_name, params: { tournament_id: empty_tournament.to_param, name: 'anything' } get :by_name, params: { tournament_id: empty_tournament.to_param, name: 'anything' }
expect(response).to have_http_status(:not_found) expect(response).to have_http_status(:not_found)
end end
@ -66,7 +66,7 @@ RSpec.describe BeamersController, type: :controller do
describe 'POST #create' do describe 'POST #create' do
let(:valid_params) do let(:valid_params) do
{ name: 'new-beamer', display_state: 'groups', is_default: false } { name: 'new-beamer', display_state: 'groups' }
end end
context 'as owner' do context 'as owner' do

View File

@ -259,6 +259,14 @@ RSpec.describe TournamentsController, type: :controller do
expect(included_teams).to match_array(@teams) expect(included_teams).to match_array(@teams)
end end
it 'creates a default beamer automatically' do
post :create, params: create_playoff_tournament_data
body = deserialize_response response
tournament = Tournament.find(body[:id])
expect(tournament.beamers.pluck(:name)).to include(Beamer::DEFAULT_NAME)
end
context 'with parameter group_stage=true' do context 'with parameter group_stage=true' do
before do before do
post :create, params: create_group_tournament_data post :create, params: create_group_tournament_data

View File

@ -4,12 +4,14 @@ FactoryBot.define do
factory :beamer do factory :beamer do
sequence(:name) { |n| "beamer-#{n}" } sequence(:name) { |n| "beamer-#{n}" }
display_state { 'matches_all' } display_state { 'matches_all' }
is_default { false }
tournament tournament
factory :default_beamer do factory :default_beamer do
name { 'default' } name { Beamer::DEFAULT_NAME }
is_default { true }
initialize_with do
tournament.beamers.find_or_initialize_by(name: name)
end
end end
end end
end end

View File

@ -36,19 +36,6 @@ RSpec.describe Beamer, type: :model do
it { should belong_to :tournament } it { should belong_to :tournament }
end end
describe 'scopes' do
describe '.default_beamer' do
it 'returns only default beamers' do
tournament = create(:tournament)
default = create(:default_beamer, tournament: tournament)
create(:beamer, tournament: tournament)
result = tournament.beamers.default_beamer
expect(result).to contain_exactly(default)
end
end
end
describe '#owner' do describe '#owner' do
it 'delegates to tournament' do it 'delegates to tournament' do
beamer = create(:beamer) beamer = create(:beamer)

View File

@ -32,6 +32,14 @@ RSpec.describe Tournament, type: :model do
it { should have_many :beamers } it { should have_many :beamers }
end end
describe 'default beamer' do
it 'creates a default beamer automatically' do
tournament = create(:tournament)
expect(tournament.beamers.pluck(:name)).to include(Beamer::DEFAULT_NAME)
end
end
describe '#matches' do describe '#matches' do
context 'group stage tournament' do context 'group stage tournament' do
before do before do