Add ranking mode
This commit is contained in:
@@ -54,10 +54,6 @@ class CompletionsController < ApplicationController
|
||||
redirect_to @contest, notice: t("completions.edit.notice")
|
||||
end
|
||||
else
|
||||
if @contestant
|
||||
@action_name = t("helpers.buttons.back_to_contestant")
|
||||
@action_path = edit_contest_contestant_path(@contest, @contestant)
|
||||
end
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
12
app/controllers/concerns/contestants_concern.rb
Normal file
12
app/controllers/concerns/contestants_concern.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module ContestantsConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def ranked_contestants(contest)
|
||||
contest.contestants.sort_by { |contestant| [
|
||||
-contestant.completions.where(remaining_pieces: nil).size,
|
||||
(contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds,
|
||||
contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? contestant.completions[-1].remaining_pieces : 1000000,
|
||||
contestant.time_seconds
|
||||
] }
|
||||
end
|
||||
end
|
||||
@@ -1,4 +1,6 @@
|
||||
class ContestantsController < ApplicationController
|
||||
include ContestantsConcern
|
||||
|
||||
before_action :set_contest
|
||||
before_action :set_contestant, only: %i[ destroy edit update]
|
||||
before_action :set_completions, only: %i[edit update ]
|
||||
@@ -6,12 +8,7 @@ class ContestantsController < ApplicationController
|
||||
def index
|
||||
authorize @contest
|
||||
|
||||
@contestants = @contest.contestants.sort_by { |contestant| [
|
||||
-contestant.completions.where(remaining_pieces: nil).size,
|
||||
(contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds,
|
||||
contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? contestant.completions[-1].remaining_pieces : 1000000,
|
||||
contestant.time_seconds
|
||||
] }
|
||||
@contestants = @contest.contestants.sort_by { |contestant| contestant.name }
|
||||
filter_contestants_per_category
|
||||
end
|
||||
|
||||
@@ -111,12 +108,7 @@ class ContestantsController < ApplicationController
|
||||
def export
|
||||
authorize @contest
|
||||
|
||||
@contestants = @contest.contestants.sort_by { |contestant| [
|
||||
-contestant.completions.where(remaining_pieces: nil).size,
|
||||
(contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds,
|
||||
contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? contestant.completions[-1].remaining_pieces : 1000000,
|
||||
contestant.time_seconds
|
||||
] }
|
||||
@contestants = ranked_contestants(@contest)
|
||||
|
||||
respond_to do |format|
|
||||
format.csv do
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
class ContestsController < ApplicationController
|
||||
include CompletionsConcern
|
||||
include ContestantsConcern
|
||||
|
||||
before_action :set_contest, only: %i[ destroy show update ]
|
||||
before_action :set_contest, only: %i[ destroy show ]
|
||||
before_action :set_settings_contest, only: %i[ settings_general_edit settings_general_update settings_offline_edit settings_offline_update settings_categories_edit ]
|
||||
before_action :offline_setup, only: %i[ offline_new offline_create offline_edit offline_update offline_completed ]
|
||||
skip_before_action :require_authentication, only: %i[ scoreboard offline_new offline_create offline_edit offline_update offline_completed ]
|
||||
@@ -60,7 +61,7 @@ class ContestsController < ApplicationController
|
||||
def create
|
||||
authorize :contest
|
||||
|
||||
@contest = Contest.new(contest_params)
|
||||
@contest = Contest.new(new_contest_params)
|
||||
@contest.user_id = current_user.id
|
||||
if @contest.save
|
||||
redirect_to "/contests/#{@contest.id}/settings/general", notice: t("contests.new.notice")
|
||||
@@ -69,16 +70,6 @@ class ContestsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @contest
|
||||
|
||||
if @contest.update(contest_params)
|
||||
redirect_to @contest, notice: t("contests.edit.notice")
|
||||
else
|
||||
render :edit, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @contest
|
||||
|
||||
@@ -97,12 +88,7 @@ class ContestsController < ApplicationController
|
||||
I18n.locale = @contest.lang
|
||||
|
||||
@title = I18n.t("contests.scoreboard.title", name: @contest.name)
|
||||
@contestants = @contest.contestants.sort_by { |contestant| [
|
||||
-contestant.completions.where(remaining_pieces: nil).size,
|
||||
(contestant.completions.where(remaining_pieces: nil).size == @contest.puzzles.length ? 1 : 0) * contestant.time_seconds,
|
||||
contestant.completions.size > 0 && contestant.completions[-1].remaining_pieces ? contestant.completions[-1].remaining_pieces : 1000000,
|
||||
contestant.time_seconds
|
||||
] }
|
||||
@contestants = ranked_contestants(@contest)
|
||||
filter_contestants_per_category
|
||||
if params.key?(:hide_offline) && params[:hide_offline] == "true"
|
||||
@contestants = @contestants.select { |contestant| !contestant.offline.present? }
|
||||
@@ -212,12 +198,12 @@ class ContestsController < ApplicationController
|
||||
@contest = Contest.find(params[:contest_id])
|
||||
end
|
||||
|
||||
def contest_params
|
||||
params.expect(contest: [ :lang, :name, :offline_form, :public, :team, :allow_registration ])
|
||||
def new_contest_params
|
||||
params.expect(contest: [ :name ])
|
||||
end
|
||||
|
||||
def settings_general_params
|
||||
params.expect(contest: [ :lang, :name, :public, :team, :allow_registration ])
|
||||
params.expect(contest: [ :lang, :name, :public, :ranking_mode, :team, :allow_registration ])
|
||||
end
|
||||
|
||||
def settings_offline_params
|
||||
|
||||
3
app/lib/ranking.rb
Normal file
3
app/lib/ranking.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
module Ranking
|
||||
AVAILABLE_RANKING_MODES = [ { id: "actual", name: I18n.t("lib.ranking.actual") }, { id: "theorical", name: I18n.t("lib.ranking.theorical") } ]
|
||||
end
|
||||
@@ -8,6 +8,7 @@
|
||||
# name :string
|
||||
# offline_form :boolean default(FALSE)
|
||||
# public :boolean default(FALSE)
|
||||
# ranking_mode :string
|
||||
# slug :string
|
||||
# team :boolean default(FALSE)
|
||||
# created_at :datetime not null
|
||||
@@ -38,6 +39,7 @@ class Contest < ApplicationRecord
|
||||
|
||||
validates :name, presence: true
|
||||
validates :lang, inclusion: { in: Languages::AVAILABLE_LANGUAGES.map { |lang| lang[:id] } }
|
||||
validates :ranking_mode, inclusion: { in: Ranking::AVAILABLE_RANKING_MODES.map { |lang| lang[:id] } }
|
||||
|
||||
generates_token_for :token
|
||||
end
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
.form-check.form-switch
|
||||
= form.check_box :public, class: "form-check-input"
|
||||
= form.label :public
|
||||
.row.mb-3
|
||||
.col
|
||||
.form-floating
|
||||
= form.select :ranking_mode, Ranking::AVAILABLE_RANKING_MODES.map { |mode| [ mode[:name], mode[:id] ] }, {}, class: "form-select"
|
||||
= form.label :ranking_mode
|
||||
.row.mb-3
|
||||
.col
|
||||
.form-check.form-switch
|
||||
|
||||
@@ -58,6 +58,7 @@ en:
|
||||
offline_form_description: Offline participants will have to fill the form by providing an image taken of the puzzle before starting solving it, and validate their finish time with an upload of an image of the completed puzzle
|
||||
offline_form_warning: Only for single-puzzle contests
|
||||
public: Enable the public scoreboard
|
||||
ranking_mode: Ranking mode (public scoreboard & CSV exports)
|
||||
team: Team contest
|
||||
team_description: For UI display purposes mainly
|
||||
allow_registration: Allow registration
|
||||
@@ -259,6 +260,10 @@ en:
|
||||
field: Field
|
||||
none: No field selected
|
||||
rank: Rank
|
||||
lib:
|
||||
ranking:
|
||||
actual: First by time if completed, then by number of pieces assembled
|
||||
theorical: By time only (projected time calculated with the ppm count)
|
||||
messages:
|
||||
index:
|
||||
no_messages: No messages received yet
|
||||
|
||||
@@ -29,6 +29,7 @@ fr:
|
||||
offline_form_description: Les participant.e.s hors-ligne pourront participer en prenant une photo du puzzle avant de le commencer, puis valider leur temps avec une photo du puzzle une fois complété
|
||||
offline_form_warning: Activable uniquement pour les concours avec un seul puzzle
|
||||
public: Activer le classement public
|
||||
ranking_mode: Mode de classement (classement public & exports CSV)
|
||||
team: Concours par équipes
|
||||
team_description: Principalement pour des raisons d'affichage
|
||||
allow_registration: Autoriser l'inscription via l'interface
|
||||
@@ -230,6 +231,10 @@ fr:
|
||||
field: Champ
|
||||
none: Aucun champ sélectionné
|
||||
rank: Rang
|
||||
lib:
|
||||
ranking:
|
||||
actual: Par temps d'abord, puis par nombre de pièces assemblées
|
||||
theorical: Par temps uniquement (temps projeté calculé à partir de la vitesse d'assemblage)
|
||||
messages:
|
||||
index:
|
||||
no_messages: Pas de messages reçus pour le moment
|
||||
|
||||
5
db/migrate/20251114085123_add_ranking_mode_to_contest.rb
Normal file
5
db/migrate/20251114085123_add_ranking_mode_to_contest.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddRankingModeToContest < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :contests, :ranking_mode, :string
|
||||
end
|
||||
end
|
||||
3
db/schema.rb
generated
3
db/schema.rb
generated
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_11_10_110151) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_11_14_085123) do
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
t.string "record_type", null: false
|
||||
@@ -95,6 +95,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_11_10_110151) do
|
||||
t.string "lang", default: "en"
|
||||
t.boolean "public", default: false
|
||||
t.boolean "offline_form", default: false
|
||||
t.string "ranking_mode"
|
||||
t.index ["slug"], name: "index_contests_on_slug", unique: true
|
||||
t.index ["user_id"], name: "index_contests_on_user_id"
|
||||
end
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
# name :string
|
||||
# offline_form :boolean default(FALSE)
|
||||
# public :boolean default(FALSE)
|
||||
# ranking_mode :string
|
||||
# slug :string
|
||||
# team :boolean default(FALSE)
|
||||
# created_at :datetime not null
|
||||
|
||||
Reference in New Issue
Block a user