Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WCA Live Integration Pre Work #2: Add liveResult and liveAttempt models #10685

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
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: 14 additions & 3 deletions app/jobs/add_live_result_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@ class AddLiveResultJob < ApplicationJob
self.queue_adapter = :shoryuken if WcaLive.sqs_queued?
queue_as EnvConfig.LIVE_QUEUE if WcaLive.sqs_queued?

def perform(params)
ActionCable.server.broadcast(WcaLive.broadcast_key(params[:round_id]),
{ attempts: params[:results], user_id: params[:user_id] })
def perform(results, round_id, registration_id, entered_by)
attempts = results.map.with_index(1) { |r, i| LiveAttempt.build(result: r, attempt_number: i, entered_by: entered_by, entered_at: Time.now.utc) }
round = Round.find(round_id)
event = round.event
format = round.format

r = Result.build({ value1: results[0], value2: results[1] || 0, value3: results[2] || 0, value4: results[3] || 0, value5: results[4] || 0, event_id: event.id, round_type_id: round.round_type_id, format_id: format.id })
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved

LiveResult.create!(registration_id: registration_id,
round: round,
live_attempts: attempts,
last_attempt_entered_at: Time.now.utc,
best: r.compute_correct_best,
average: r.compute_correct_average)
end
end
28 changes: 28 additions & 0 deletions app/models/live_attempt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

class LiveAttempt < ApplicationRecord
include Comparable

belongs_to :live_result
# If the Attempt has been replaced, it no longer points to a live_result, but instead is being pointed to
FinnIckler marked this conversation as resolved.
Show resolved Hide resolved
# by another Attempt
belongs_to :replaced_by, class_name: "LiveAttempt", optional: true

belongs_to :entered_by, class_name: 'User'

validates :result, presence: true
validates :result, numericality: { only_integer: true }
validates :attempt_number, numericality: { only_integer: true }

DEFAULT_SERIALIZE_OPTIONS = {
only: %w[attempt_number result],
}.freeze

def serializable_hash(options = nil)
super(DEFAULT_SERIALIZE_OPTIONS.merge(options || {}))
end

def <=>(other)
result <=> other.result
end
end
48 changes: 48 additions & 0 deletions app/models/live_result.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

class LiveResult < ApplicationRecord
has_many :live_attempts, -> { where(replaced_by: nil).order(:attempt_number) }

after_save :notify_users

belongs_to :registration

belongs_to :round

alias_attribute :result_id, :id

has_one :event, through: :round

DEFAULT_SERIALIZE_OPTIONS = {
only: %w[ranking registration_id round_id best average single_record_tag average_record_tag advancing advancing_questionable entered_at entered_by_id],
methods: %w[event_id attempts result_id],
include: %w[],
}.freeze

def serializable_hash(options = nil)
super(DEFAULT_SERIALIZE_OPTIONS.merge(options || {}))
end

def event_id
event.id
end

def attempts
live_attempts.order(:attempt_number)
end

def potential_score
rank_by = round.format.sort_by == 'single' ? 'best' : 'average'
complete? ? self[rank_by.to_sym] : best_possible_score
end

def complete?
live_attempts.where.not(result: 0).count == round.format.expected_solve_count
end

private

def notify_users
ActionCable.server.broadcast("results_#{round_id}", serializable_hash)
end
end
1 change: 1 addition & 0 deletions app/models/registration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Registration < ApplicationRecord
has_many :registration_payments
has_many :competition_events, through: :registration_competition_events
has_many :events, through: :competition_events
has_many :live_results
has_many :assignments, as: :registration, dependent: :delete_all
has_many :wcif_extensions, as: :extendable, dependent: :delete_all
has_many :payment_intents, as: :holder, dependent: :delete_all
Expand Down
2 changes: 2 additions & 0 deletions app/models/round.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def format

has_many :wcif_extensions, as: :extendable, dependent: :delete_all

has_many :live_results

MAX_NUMBER = 4
validates_numericality_of :number,
only_integer: true,
Expand Down
30 changes: 30 additions & 0 deletions db/migrate/20250124154917_create_live_result_tables.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

class CreateLiveResultTables < ActiveRecord::Migration[7.2]
def change
create_table :live_results do |t|
t.references :registration, null: false, index: true
t.references :round, null: false, index: true
t.datetime :last_attempt_entered_at, null: false
t.integer :ranking
t.integer :best, null: false
t.integer :average, null: false
t.string :single_record_tag, limit: 255
t.string :average_record_tag, limit: 255
t.boolean :advancing, default: false, null: false
t.boolean :advancing_questionable, default: false, null: false
t.index [:registration_id, :round_id], unique: true
t.timestamps
end

create_table :live_attempts do |t|
t.integer :result, null: false
t.integer :attempt_number, null: false
t.references :replaced_by, foreign_key: { to_table: :live_attempts }
t.datetime :entered_at, null: false
t.references :entered_by, type: :integer, null: false, foreign_key: { to_table: :users }
t.references :live_result, null: false
t.timestamps
end
end
end
36 changes: 35 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.2].define(version: 2024_11_24_050607) do
ActiveRecord::Schema[7.2].define(version: 2025_02_02_154917) do
create_table "Competitions", id: { type: :string, limit: 32, default: "" }, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.string "name", limit: 50, default: "", null: false
t.string "cityName", limit: 50, default: "", null: false
Expand Down Expand Up @@ -785,6 +785,38 @@
t.index ["jti"], name: "index_jwt_denylist_on_jti"
end

create_table "live_attempts", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.integer "result", null: false
t.integer "attempt_number", null: false
t.bigint "replaced_by_id"
t.datetime "entered_at", null: false
t.integer "entered_by_id", null: false
t.bigint "live_result_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["entered_by_id"], name: "index_live_attempts_on_entered_by_id"
t.index ["live_result_id"], name: "index_live_attempts_on_live_result_id"
t.index ["replaced_by_id"], name: "index_live_attempts_on_replaced_by_id"
end

create_table "live_results", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.bigint "registration_id", null: false
t.bigint "round_id", null: false
t.datetime "last_attempt_entered_at", null: false
t.integer "ranking"
t.integer "best", null: false
t.integer "average", null: false
t.string "single_record_tag", limit: 255
t.string "average_record_tag", limit: 255
t.boolean "advancing", default: false, null: false
t.boolean "advancing_questionable", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["registration_id", "round_id"], name: "index_live_results_on_registration_id_and_round_id", unique: true
t.index ["registration_id"], name: "index_live_results_on_registration_id"
t.index ["round_id"], name: "index_live_results_on_round_id"
end

create_table "locations", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "latitude_microdegrees"
Expand Down Expand Up @@ -1394,6 +1426,8 @@

add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "live_attempts", "live_attempts", column: "replaced_by_id"
add_foreign_key "live_attempts", "users", column: "entered_by_id"
add_foreign_key "microservice_registrations", "Competitions", column: "competition_id", on_update: :cascade, on_delete: :cascade
add_foreign_key "microservice_registrations", "users"
add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", on_delete: :cascade
Expand Down
2 changes: 2 additions & 0 deletions lib/database_dumper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ def self.actions_to_column_sanitizers(columns_by_action)
),
),
}.freeze,
"live_results" => :skip_all_rows,
"live_attempts" => :skip_all_rows,
"schedule_activities" => {
where_clause: "WHERE (holder_type=\"ScheduleActivity\" AND holder_id IN (#{VISIBLE_ACTIVITY_IDS}) or id in (#{VISIBLE_ACTIVITY_IDS}))",
column_sanitizers: actions_to_column_sanitizers(
Expand Down
20 changes: 20 additions & 0 deletions spec/factories/live_attempts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

FactoryBot.define do
factory :live_attempt do
association :entered_by, factory: [:user, :wca_id]
entered_at { Time.now.utc }

result { 3000 }
attempt_number { 1 }

trait :mbf do
# 9 points in 4 minutes
result { 900_024_000 }
end

trait :fm do
result { 35 }
end
end
end
35 changes: 35 additions & 0 deletions spec/factories/live_results.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

FactoryBot.define do
factory :live_result do
association :registration, factory: [:registration]
round { FactoryBot.create(:round, event_id: '333oh', format_id: 'a') } # Ensure the round exists

best { 3000 }
average { 5000 }
last_attempt_entered_at { Time.now.utc }

transient do
attempts_count { 5 }
end

after(:create) do |live_result, evaluator|
create_list(:live_attempt, evaluator.attempts_count, live_result: live_result)
end

trait :mo3 do
round { FactoryBot.create(:round, event_id: '666', format_id: 'm') }
average { best }

transient do
attempts_count { 3 }
end
end

trait :incomplete do
transient do
attempts_count { 3 }
end
end
end
end
17 changes: 17 additions & 0 deletions spec/models/live_result_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe LiveResult do
describe "calculates if live results are complete" do
it "for rounds with means" do
complete_mean_result = FactoryBot.create(:live_result, :mo3)
expect(complete_mean_result.complete?).to eq true
end

it "if less than 5 attempts are entered for an average of 5 round" do
incomplete_ao5_result = FactoryBot.create(:live_result, :incomplete)
expect(incomplete_ao5_result.complete?).to eq false
end
end
end
Loading