Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 16 additions & 1 deletion app/controllers/contest_descriptions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,22 @@ def create
end

def update
handle_save(@contest_description.update(contest_description_params), 'updated')
# Check if trying to deactivate with active instances
if contest_description_params[:active] == "0" && @contest_description.active_contest_instances.any?
flash.now[:alert] = "Cannot deactivate contest description while it has active instances. Please deactivate all instances first."

respond_to do |format|
format.turbo_stream {
render turbo_stream: [
turbo_stream.replace('flash', partial: 'shared/flash_messages'),
turbo_stream.replace('contest_description_form', partial: 'contest_descriptions/form', locals: { contest_description: @contest_description })
], status: :unprocessable_entity
}
format.html { render :edit, status: :unprocessable_entity }
end
else
handle_save(@contest_description.update(contest_description_params), 'updated')
end
end

def destroy
Expand Down
17 changes: 16 additions & 1 deletion app/controllers/contest_instances_controller.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class ContestInstancesController < ApplicationController
before_action :set_container
before_action :set_contest_description
before_action :set_contest_instance, only: %i[show edit update destroy send_round_results]
before_action :set_contest_instance, only: %i[show edit update destroy send_round_results deactivate]
before_action :authorize_container_access

# GET /contest_instances
Expand Down Expand Up @@ -213,6 +213,21 @@ def export_round_results
end
end

def deactivate
@contest_instance.update(active: false)
@contest_description = @contest_instance.contest_description
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.replace(
'active-instances-list',
partial: 'contest_descriptions/active_instances_inline',
locals: { contest_description: @contest_description }
)
end
format.html { redirect_to container_contest_description_path(@contest_description.container, @contest_description), notice: 'Instance deactivated.' }
end
end

private

def authorize_container_access
Expand Down
13 changes: 13 additions & 0 deletions app/models/contest_description.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ class ContestDescription < ApplicationRecord

validates :created_by, presence: true
validates :name, presence: true, uniqueness: true
validate :cannot_deactivate_with_active_instances

scope :active, -> { where(active: true) }

def active_contest_instances
contest_instances.where(active: true)
end

private

def cannot_deactivate_with_active_instances
if will_save_change_to_active? && !active && contest_instances.where(active: true).exists?
errors.add(:active, 'Cannot deactivate contest description while instances are active.')
end
end
end
15 changes: 15 additions & 0 deletions app/models/contest_instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class ContestInstance < ApplicationRecord
validate :must_have_at_least_one_category
validate :only_one_active_per_contest_description
validate :date_closed_after_date_open
validate :cannot_activate_if_description_inactive

# Scopes
scope :active_and_open, -> {
Expand Down Expand Up @@ -88,6 +89,14 @@ class ContestInstance < ApplicationRecord
where.not(id: maxed_out_contest_instance_ids)
}

scope :active_for_contest_description, ->(contest_description_id) {
where(active: true, contest_description_id: contest_description_id)
}

scope :for_contest_description, ->(contest_description_id) {
where(contest_description_id: contest_description_id)
}

def open?
active && Time.current.between?(date_open, date_closed)
end
Expand Down Expand Up @@ -171,4 +180,10 @@ def date_closed_after_date_open
errors.add(:date_closed, 'must be after date contest opens')
end
end

def cannot_activate_if_description_inactive
if active && contest_description && !contest_description.active?
errors.add(:active, 'Cannot activate a contest instance when its contest description is inactive.')
end
end
end
5 changes: 5 additions & 0 deletions app/views/containers/_container_detail.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@
</label>
</div>

<div class="mb-3">
This collection has <span class="fw-bolder small"><%= pluralize(container_contest_descriptions.active.count, "active description") %></span>
and <span class="fw-bolder small"><%= pluralize(container_contest_descriptions.count - container_contest_descriptions.active.count, "inactive description") %></span>.
</div>

<div class="container w-100 mx-auto">
<% if container_contest_descriptions.present? %>
<%= render 'contest_descriptions_table',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<turbo-frame id="active-instances-list">
<% active_instances = contest_description.active_contest_instances %>
<% if active_instances.any? %>
<div class="mt-4">
<h5>Active Contest Instances</h5>
<ul class="list-group">
<% active_instances.each do |instance| %>
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>
<strong><%= instance.display_name %></strong>
<span class="text-muted small">[<%= l(instance.date_open, format: :short) %> - <%= l(instance.date_closed, format: :short) %>]</span>
</span>
<%= button_to 'Deactivate',
deactivate_container_contest_description_contest_instance_path(
contest_description.container, contest_description, instance
),
method: :patch,
data: { turbo_frame: 'active-instances-list' },
class: 'btn btn-danger btn-sm' %>
</li>
<% end %>
</ul>
</div>
<% else %>
<div class="mt-4 text-muted">No active contest instances.</div>
<% end %>
</turbo-frame>
7 changes: 6 additions & 1 deletion app/views/contest_descriptions/_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<div id="contest_description_form" class="mb-1">
<%= simple_form_for([@container, @contest_description], html: { id: 'new_contest_description_form' }) do |f| %>
<%= simple_form_for([@container, @contest_description],
html: {
id: 'new_contest_description_form'
}) do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>

Expand All @@ -18,3 +21,5 @@
</div>
<% end %>
</div>

<%= render 'contest_descriptions/active_instances_inline', contest_description: @contest_description %>
2 changes: 1 addition & 1 deletion app/views/contest_descriptions/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
</div>
<div id="contest_description_form">
<%= render 'form' %>
</div>
</div>
4 changes: 4 additions & 0 deletions app/views/contest_instances/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
Show only <span class="fw-bolder">Active</span> contest instances
</label>
</div>
<div class="mb-3">
This contest has <span class="fw-bolder small"><%= pluralize(@contest_instances.active_for_contest_description(@contest_description.id).count, "active instance") %></span>
and <span class="fw-bolder small"><%= pluralize(@contest_instances.for_contest_description(@contest_description.id).count - @contest_instances.active_for_contest_description(@contest_description.id).count, "inactive instance") %></span>.
</div>

<div id="contest_instances" class="table-responsive">
<table class="table table-striped table-bordered">
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
post 'send_round_results'
get :export_entries
get :export_round_results
patch :deactivate
end
resources :judging_rounds do
member do
Expand Down
8 changes: 4 additions & 4 deletions spec/controllers/contest_instances_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
let(:department) { create(:department) }
let(:user) { create(:user, :axis_mundi) } # Admin user with full privileges
let(:container) { create(:container, department: department) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }
let(:contest_instance) { create(:contest_instance, contest_description: contest_description) }
let(:judging_round) { create(:judging_round, contest_instance: contest_instance, completed: true) }

Expand Down Expand Up @@ -72,7 +72,7 @@
let(:department) { create(:department) }
let(:user) { create(:user, :axis_mundi) } # Admin user with full privileges
let(:container) { create(:container, department: department) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }
let(:contest_instance) { create(:contest_instance, contest_description: contest_description) }

before do
Expand Down Expand Up @@ -243,7 +243,7 @@
describe 'GET #export_entries' do
let(:department) { create(:department) }
let(:container) { create(:container, department: department) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }

context 'with authorized users' do
let(:user) { create(:user, :axis_mundi) }
Expand Down Expand Up @@ -514,7 +514,7 @@
describe 'GET #export_round_results' do
let(:department) { create(:department) }
let(:container) { create(:container, department: department) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }
let(:contest_instance) { create(:contest_instance, contest_description: contest_description) }
let(:judging_round) { create(:judging_round, contest_instance: contest_instance, completed: true) }

Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/entry_rankings_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

RSpec.describe EntryRankingsController, type: :controller do
let(:container) { create(:container) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }
let(:contest_instance) { create(:contest_instance, contest_description: contest_description) }
let(:judging_round) { create(:judging_round, contest_instance: contest_instance) }
let(:entry) { create(:entry, contest_instance: contest_instance) }
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/judge_dashboard_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

RSpec.describe JudgeDashboardController, type: :controller do
let(:container) { create(:container) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }

# Start at a known point in time
before(:all) do
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/judging_assignments_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

RSpec.describe JudgingAssignmentsController, type: :controller do
let(:container) { create(:container) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }
let(:contest_instance) { create(:contest_instance, contest_description: contest_description) }
let(:admin) { create(:user) }
let!(:judge_role) { create(:role, kind: 'Judge') }
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/judging_rounds_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

RSpec.describe JudgingRoundsController, type: :controller do
let(:container) { create(:container) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }
let(:contest_instance) { create(:contest_instance, contest_description: contest_description) }
let(:judging_round) { create(:judging_round, contest_instance: contest_instance) }
let(:admin) { create(:user) }
Expand Down
16 changes: 8 additions & 8 deletions spec/features/bulk_contest_instance_creation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
end

it "shows only contest descriptions that have existing instances" do
description1 = create(:contest_description, container: @container)
description2 = create(:contest_description, container: @container)
description1 = create(:contest_description, :active, container: @container)
description2 = create(:contest_description, :active, container: @container)
create(:contest_instance, contest_description: description1)
create(:contest_instance, contest_description: description2)

Expand All @@ -22,13 +22,13 @@
expect(page).to have_content(description2.name)

# Create a new contest description without instances
new_description = create(:contest_description, container: @container)
new_description = create(:contest_description, :active, container: @container)
expect(page).to have_no_content(new_description.name)
end

it "allows selection of multiple contest descriptions" do
description1 = create(:contest_description, container: @container)
description2 = create(:contest_description, container: @container)
description1 = create(:contest_description, :active, container: @container)
description2 = create(:contest_description, :active, container: @container)
create(:contest_instance, contest_description: description1)
create(:contest_instance, contest_description: description2)

Expand All @@ -42,7 +42,7 @@
end

it "requires date selection" do
description = create(:contest_description, container: @container)
description = create(:contest_description, :active, container: @container)
create(:contest_instance, contest_description: description)

visit new_container_bulk_contest_instance_path(@container)
Expand All @@ -55,7 +55,7 @@
end

it "creates new instances based on most recent existing instances" do
description = create(:contest_description, container: @container)
description = create(:contest_description, :active, container: @container)
existing_instance = create(:contest_instance, contest_description: description)
new_open_date = 1.month.from_now
new_close_date = 2.months.from_now
Expand All @@ -78,7 +78,7 @@
end

it "validates date ranges" do
description = create(:contest_description, container: @container)
description = create(:contest_description, :active, container: @container)
create(:contest_instance, contest_description: description)

visit new_container_bulk_contest_instance_path(@container)
Expand Down
2 changes: 1 addition & 1 deletion spec/features/judging_round_selection_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

RSpec.describe 'Judging Round Selection', type: :system do
let(:container) { create(:container) }
let(:contest_description) { create(:contest_description, container: container) }
let(:contest_description) { create(:contest_description, :active, container: container) }
let(:contest_instance) { create(:contest_instance, contest_description: contest_description) }
let(:judging_round) do
create(:judging_round,
Expand Down
2 changes: 1 addition & 1 deletion spec/mailers/results_mailer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
let(:profile) { create(:profile, user: user) }
let(:department) { create(:department) }
let(:container) { create(:container, name: 'Test Container', department: department, contact_email: 'contact@example.com') }
let(:contest_description) { create(:contest_description, name: 'Test Contest', container: container) }
let(:contest_description) { create(:contest_description, :active, name: 'Test Contest', container: container) }
let(:contest_instance) { create(:contest_instance, contest_description: contest_description, date_open: 2.months.ago, date_closed: 1.month.ago) }
let(:category) { create(:category, kind: 'Poetry') }
let(:entry) { create(:entry, title: 'Test Entry', profile: profile, contest_instance: contest_instance, category: category, pen_name: 'Writer Pen') }
Expand Down
18 changes: 14 additions & 4 deletions spec/models/container_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,22 @@
end

it 'excludes entries from inactive contest descriptions' do
inactive_desc = create(:contest_description, container: container, active: false)
inactive_instance = create(:contest_instance, contest_description: inactive_desc, active: true)
create(:entry, profile: create(:profile, campus: campus1), contest_instance: inactive_instance)
# Create an active contest description with an active instance and entry
active_desc = create(:contest_description, container: container, active: true)
active_instance = create(:contest_instance, contest_description: active_desc, active: true)
create(:entry, profile: create(:profile, campus: campus1), contest_instance: active_instance)

# Verify the entry is included initially
summary = container.entries_summary
expect(summary.find { |s| s.campus_descr == 'Campus A' }.entry_count).to eq(2)
expect(summary.find { |s| s.campus_descr == 'Campus A' }.entry_count).to eq(3) # 2 from setup + 1 new

# Deactivate both the instance and description (business rule: must deactivate instance first)
active_instance.update!(active: false)
active_desc.update!(active: false)

# Now verify that entries from the deactivated contest description are excluded
summary = container.entries_summary
expect(summary.find { |s| s.campus_descr == 'Campus A' }.entry_count).to eq(2) # Back to original 2
end
end

Expand Down
23 changes: 23 additions & 0 deletions spec/models/contest_description_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,27 @@
end
end
end

describe 'deactivation with active instances' do
let(:container) { create(:container) }
let!(:description) { create(:contest_description, container: container, active: true) }
let!(:active_instance) { create(:contest_instance, contest_description: description, active: true) }
let!(:inactive_instance) { create(:contest_instance, contest_description: description, active: false) }

it 'is not valid to deactivate if any contest_instance is active' do
description.active = false
expect(description).not_to be_valid
expect(description.errors[:active]).to include('Cannot deactivate contest description while instances are active.')
end

it 'is valid to deactivate if all contest_instances are inactive' do
active_instance.update!(active: false)
description.active = false
expect(description).to be_valid
end

it 'returns only active contest_instances from #active_contest_instances' do
expect(description.active_contest_instances).to contain_exactly(active_instance)
end
end
end
Loading
Loading