diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 7fa52799db..f8b1333562 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -170,7 +170,6 @@ def records DAY(competition.start_date) day, event.id eventId, event.name eventName, - event.cellName eventCellName, result.type type, result.value value, result.formatId formatId, @@ -224,7 +223,6 @@ def records result.*, value, event.name eventName, - event.cellName eventCellName, format, country.name countryName, competition.cellName competitionName, diff --git a/app/models/championship.rb b/app/models/championship.rb index 01b2abde08..844a4222b7 100644 --- a/app/models/championship.rb +++ b/app/models/championship.rb @@ -5,11 +5,11 @@ class Championship < ApplicationRecord CHAMPIONSHIP_TYPE_WORLD = "world" MAJOR_CHAMPIONSHIP_TYPES = [ CHAMPIONSHIP_TYPE_WORLD, - *Continent.real.map(&:id), + *Continent::REAL_CONTINENTS.pluck(:id), ].freeze CHAMPIONSHIP_TYPES = [ *MAJOR_CHAMPIONSHIP_TYPES, - *Country.real.map(&:iso2), + *Country::WCA_COUNTRIES.pluck(:iso2), *EligibleCountryIso2ForChampionship.championship_types, ].freeze @@ -58,13 +58,4 @@ def to_a def <=>(other) self.to_a <=> other.to_a end - - def self.grouped_championship_types - { - "planetary" => [CHAMPIONSHIP_TYPE_WORLD], - "continental" => Continent.all_sorted_by(I18n.locale, real: true).map(&:id), - "multi-national" => EligibleCountryIso2ForChampionship.championship_types, - "national" => Country.all_sorted_by(I18n.locale, real: true).map(&:iso2), - } - end end diff --git a/app/models/competition.rb b/app/models/competition.rb index ef52e88591..93d7b17347 100644 --- a/app/models/competition.rb +++ b/app/models/competition.rb @@ -176,6 +176,11 @@ class Competition < ApplicationRecord MAX_ID_LENGTH = 32 MAX_NAME_LENGTH = 50 MAX_CELL_NAME_LENGTH = 32 + MAX_CITY_NAME_LENGTH = 50 + MAX_VENUE_LENGTH = 240 + MAX_FREETEXT_LENGTH = 191 + MAX_URL_LENGTH = 200 + MAX_MARKDOWN_LENGTH = 255 MAX_COMPETITOR_LIMIT = 5000 MAX_GUEST_LIMIT = 100 validates_inclusion_of :competitor_limit_enabled, in: [true, false], if: :competitor_limit_required? @@ -197,7 +202,7 @@ class Competition < ApplicationRecord validates :external_website, format: { with: URL_RE }, allow_blank: true validates :external_registration_page, presence: true, format: { with: URL_RE }, if: :external_registration_page_required? - validates_inclusion_of :countryId, in: Country.ids.freeze + validates_inclusion_of :countryId, in: Country::ALL_STATES_RAW.pluck(:id).freeze validates :currency_code, inclusion: { in: Money::Currency, message: proc { I18n.t('competitions.errors.invalid_currency_code') } } validates_numericality_of :refund_policy_percent, greater_than_or_equal_to: 0, less_than_or_equal_to: 100, if: :refund_policy_percent_required? @@ -224,17 +229,13 @@ class Competition < ApplicationRecord validates_inclusion_of :main_event_id, in: ->(comp) { [nil].concat(comp.persisted_events_id) } # Validations are used to show form errors to the user. If string columns aren't validated for length, it produces an unexplained error for the user - # VALIDATED_COLUMNS: All columns which appear in the competition form and are editable by users - # DONT_VALIDATE_STRING_LENGTH: String columns not exposed to users in the cmopetition form - VALIDATE_STRING_LENGTH = %w[ - name cityName venue venueAddress venueDetails external_website cellName contact name_reason external_registration_page forbid_newcomers_reason - ].freeze - DONT_VALIDATE_STRING_LENGTH = %w[countryId connected_stripe_account_id currency_code main_event_id id].freeze - columns_hash.each do |column_name, column_info| - if VALIDATE_STRING_LENGTH.include?(column_name) && column_info.limit - validates column_name, length: { maximum: column_info.limit } - end - end + validates :name, length: { maximum: MAX_NAME_LENGTH } + validates :cellName, length: { maximum: MAX_CELL_NAME_LENGTH } + validates :cityName, length: { maximum: MAX_CITY_NAME_LENGTH } + validates :venue, length: { maximum: MAX_VENUE_LENGTH } + validates :venueAddress, :venueDetails, :name_reason, :forbid_newcomers_reason, length: { maximum: MAX_FREETEXT_LENGTH } + validates :external_website, :external_registration_page, length: { maximum: MAX_URL_LENGTH } + validates :contact, length: { maximum: MAX_MARKDOWN_LENGTH } # Dirty old trick to deal with competition id changes (see other methods using # 'with_old_id' for more details). diff --git a/app/models/concerns/static_data.rb b/app/models/concerns/static_data.rb new file mode 100644 index 0000000000..cb2b1b4269 --- /dev/null +++ b/app/models/concerns/static_data.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module StaticData + extend ActiveSupport::Concern + + DATA_FOLDER = Rails.root.join('lib/static_data') + + class_methods do + def parse_json_file(file_path, symbolize_names: true) + ::JSON.parse(File.read(file_path), symbolize_names: symbolize_names) + end + + def write_json_file(file_path, hash_data) + json_output = ::JSON.pretty_generate(hash_data.as_json) + + # Don't rewrite the file if it already exists and has the same content. + # It helps the asset pipeline or webpack understand that file wasn't changed. + if File.exist?(file_path) && File.read(file_path) == json_output + return + end + + File.write(file_path, json_output) + end + end + + included do + after_commit :write_json_data! if Rails.env.development? + + def self.data_file_handle + self.name.pluralize.underscore + end + + def self.data_file_path + DATA_FOLDER.join("#{self.data_file_handle}.json") + end + + def self.static_json_data + self.parse_json_file(self.data_file_path) + end + + def self.all_raw + self.static_json_data + end + + def self.all_raw_sanitized + column_symbols = self.column_names.map(&:to_sym) + self.all_raw.map { |attributes| attributes.slice(*column_symbols) } + end + + def self.all_static + self.all_raw_sanitized.map { |attributes| self.new(**attributes) } + end + + def self.dump_static + self.all.as_json + end + + def self.load_json_data! + self.upsert_all(self.all_raw_sanitized) + end + + def self.write_json_data! + self.write_json_file(self.data_file_path, self.dump_static) + end + end +end diff --git a/app/models/continent.rb b/app/models/continent.rb index c7f461b6af..b46498268c 100644 --- a/app/models/continent.rb +++ b/app/models/continent.rb @@ -7,6 +7,9 @@ class Continent < ApplicationRecord include Cachable include LocalizedSortable + include StaticData + + REAL_CONTINENTS = self.all_raw.select { |c| !FICTIVE_IDS.include?(c[:id]) }.freeze has_many :countries, foreign_key: :continentId diff --git a/app/models/country.rb b/app/models/country.rb index 61cbc9dd27..143e72b7c6 100644 --- a/app/models/country.rb +++ b/app/models/country.rb @@ -2,7 +2,8 @@ class Country < ApplicationRecord include Cachable - WCA_STATES_JSON_PATH = Rails.root.to_s + "/config/wca-states.json" + include StaticData + self.table_name = "Countries" has_one :wfc_dues_redirect, as: :redirect_source @@ -27,33 +28,45 @@ class Country < ApplicationRecord all_tz end.freeze - MULTIPLE_COUNTRIES = [ - { id: 'XF', name: 'Multiple Countries (Africa)', continentId: '_Africa', iso2: 'XF' }, - { id: 'XM', name: 'Multiple Countries (Americas)', continentId: '_Multiple Continents', iso2: 'XM' }, - { id: 'XA', name: 'Multiple Countries (Asia)', continentId: '_Asia', iso2: 'XA' }, - { id: 'XE', name: 'Multiple Countries (Europe)', continentId: '_Europe', iso2: 'XE' }, - { id: 'XN', name: 'Multiple Countries (North America)', continentId: '_North America', iso2: 'XN' }, - { id: 'XO', name: 'Multiple Countries (Oceania)', continentId: '_Oceania', iso2: 'XO' }, - { id: 'XS', name: 'Multiple Countries (South America)', continentId: '_South America', iso2: 'XS' }, - { id: 'XW', name: 'Multiple Countries (World)', continentId: '_Multiple Continents', iso2: 'XW' }, - ].freeze - - FICTIVE_IDS = MULTIPLE_COUNTRIES.map { |c| c[:id] }.freeze + FICTIVE_COUNTRY_DATA_PATH = StaticData::DATA_FOLDER.join("#{self.data_file_handle}.fictive.json") + MULTIPLE_COUNTRIES = self.parse_json_file(FICTIVE_COUNTRY_DATA_PATH).freeze + + FICTIVE_IDS = MULTIPLE_COUNTRIES.pluck(:id).freeze NAME_LOOKUP_ATTRIBUTE = :iso2 + include LocalizedSortable - WCA_STATES = JSON.parse(File.read(WCA_STATES_JSON_PATH)).freeze + REAL_COUNTRY_DATA_PATH = StaticData::DATA_FOLDER.join("#{self.data_file_handle}.real.json") + WCA_STATES_JSON = self.parse_json_file(REAL_COUNTRY_DATA_PATH, symbolize_names: false).freeze - ALL_STATES = [ - WCA_STATES["states_lists"].map do |list| - list["states"].map do |state| - state_id = state["id"] || I18n.transliterate(state["name"]).tr("'", "_") - { id: state_id, continentId: state["continent_id"], - iso2: state["iso2"], name: state["name"] } - end - end, + WCA_COUNTRIES = WCA_STATES_JSON["states_lists"].flat_map do |list| + list["states"].map do |state| + state_id = state["id"] || I18n.transliterate(state["name"]).tr("'", "_") + { id: state_id, continentId: state["continent_id"], + iso2: state["iso2"], name: state["name"] } + end + end + + ALL_STATES_RAW = [ + WCA_COUNTRIES, MULTIPLE_COUNTRIES, - ].flatten.map { |c| Country.new(c) }.freeze + ].flatten.freeze + + def self.all_raw + ALL_STATES_RAW + end + + # As of writing this comment, the actual `Countries` data is controlled by WRC + # and we only have control over the 'fictive' values. We parse the WRC file above and override + # the `all_raw` getter to include the real countries, but they're not part of our static dataset in the stricter sense + + def self.dump_static + MULTIPLE_COUNTRIES + end + + def self.data_file_handle + "#{self.name.pluralize.underscore}.fictive" + end belongs_to :continent, foreign_key: :continentId alias_attribute :continent_id, :continentId diff --git a/app/models/eligible_country_iso2_for_championship.rb b/app/models/eligible_country_iso2_for_championship.rb index 09522a35db..fa2fb7bf8a 100644 --- a/app/models/eligible_country_iso2_for_championship.rb +++ b/app/models/eligible_country_iso2_for_championship.rb @@ -1,14 +1,35 @@ # frozen_string_literal: true class EligibleCountryIso2ForChampionship < ApplicationRecord + include StaticData + self.table_name = "eligible_country_iso2s_for_championship" belongs_to :championship, foreign_key: :championship_type, primary_key: :championship_type, optional: true validates :eligible_country_iso2, uniqueness: { scope: :championship_type, case_sensitive: false }, - inclusion: { in: Country.all.map(&:iso2) } + inclusion: { in: Country::ALL_STATES_RAW.pluck(:iso2) } + + def self.data_file_handle + "championship_eligible_iso2" + end + + def self.all_raw + self.static_json_data.flat_map do |type, iso2_list| + iso2_list.map do |iso2| + { championship_type: type, eligible_country_iso2: iso2 } + end + end + end + + def self.dump_static + self.all + .group_by(&:championship_type) + .transform_values { |el| el.pluck(:eligible_country_iso2) } + .as_json + end def self.championship_types - pluck(:championship_type).uniq + all_raw.pluck(:championship_type).uniq end end diff --git a/app/models/event.rb b/app/models/event.rb index c37500f5f8..c8839fa1d3 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -2,6 +2,8 @@ class Event < ApplicationRecord include Cachable + include StaticData + self.table_name = "Events" has_many :competition_events @@ -23,10 +25,6 @@ def name_in(locale) I18n.t(id, scope: :events, locale: locale) end - def cellName - raise "#cellName is deprecated, and will eventually be removed. Use #name instead. See https://github.com/thewca/worldcubeassociation.org/issues/1054." - end - # Pay special attention to the difference between .. (two dots) and ... (three dots) # which map to different operators < and <= in SQL (inclusive VS exclusive range) scope :official, -> { where(rank: ...990) } @@ -70,16 +68,23 @@ def fast_event? ['333', '222', '444', '333oh', 'clock', 'mega', 'pyram', 'skewb', 'sq1'].include?(self.id) end + def self.dump_static + self.includes(:preferred_formats, :formats).order(:rank).as_json( + only: %w[id rank format], + ) + end + alias_method :can_change_time_limit, :can_change_time_limit? alias_method :can_have_cutoff, :can_have_cutoff? alias_method :is_timed_event, :timed_event? alias_method :is_fewest_moves, :fewest_moves? alias_method :is_multiple_blindfolded, :multiple_blindfolded? + alias_method :is_official, :official? DEFAULT_SERIALIZE_OPTIONS = { only: ["id"], methods: ["name", "can_change_time_limit", "can_have_cutoff", "is_timed_event", - "is_fewest_moves", "is_multiple_blindfolded", "format_ids"], + "is_fewest_moves", "is_multiple_blindfolded", "is_official", "format_ids"], }.freeze def serializable_hash(options = nil) diff --git a/app/models/format.rb b/app/models/format.rb index c1b4df9f81..f3d0760bef 100644 --- a/app/models/format.rb +++ b/app/models/format.rb @@ -2,6 +2,8 @@ class Format < ApplicationRecord include Cachable + include StaticData + self.table_name = "Formats" has_many :preferred_formats diff --git a/app/models/preferred_format.rb b/app/models/preferred_format.rb index cee595d034..7def399b91 100644 --- a/app/models/preferred_format.rb +++ b/app/models/preferred_format.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class PreferredFormat < ApplicationRecord + include StaticData + belongs_to :event belongs_to :format @@ -9,4 +11,21 @@ def format end default_scope -> { order(:ranking) } + + def self.all_raw + self.static_json_data.flat_map do |event_id, format_ids| + format_ids.map.with_index do |format_id, idx| + { event_id: event_id, format_id: format_id, ranking: idx + 1 } + end + end + end + + def self.dump_static + self.unscoped + .joins(:event) + .order(:rank) + .group_by(&:event_id) + .transform_values { |el| el.sort_by(&:ranking).pluck(:format_id) } + .as_json + end end diff --git a/app/models/round_type.rb b/app/models/round_type.rb index ae8bd7fec1..1f6f4f4d55 100644 --- a/app/models/round_type.rb +++ b/app/models/round_type.rb @@ -2,6 +2,8 @@ class RoundType < ApplicationRecord include Cachable + include StaticData + self.table_name = "RoundTypes" has_many :results, foreign_key: :roundTypeId diff --git a/app/views/regulations/countries.html.erb b/app/views/regulations/countries.html.erb index 776038d3d8..4dc745643d 100644 --- a/app/views/regulations/countries.html.erb +++ b/app/views/regulations/countries.html.erb @@ -1,8 +1,8 @@ -<% title = Country::WCA_STATES["title"] %> -<% version = Country::WCA_STATES["version"] %> -<% version_hash = Country::WCA_STATES["version_hash"] %> -<% preamble = Country::WCA_STATES["text"] %> -<% states_lists = Country::WCA_STATES["states_lists"] %> +<% title = Country::WCA_STATES_JSON["title"] %> +<% version = Country::WCA_STATES_JSON["version"] %> +<% version_hash = Country::WCA_STATES_JSON["version_hash"] %> +<% preamble = Country::WCA_STATES_JSON["text"] %> +<% states_lists = Country::WCA_STATES_JSON["states_lists"] %> <% provide(:title, title) %>

<%= title %>

diff --git a/app/webpacker/lib/wca-data.js.erb b/app/webpacker/lib/wca-data.js.erb index 1e78228650..cf53116f80 100644 --- a/app/webpacker/lib/wca-data.js.erb +++ b/app/webpacker/lib/wca-data.js.erb @@ -1,5 +1,11 @@ import _ from 'lodash'; -import I18n from './i18n' +import I18n from './i18n'; + +const camelizeKeys = (obj) => _.mapKeys(obj, (v, k) => _.camelCase(k)) + +const loadStaticData = (rawEntities) => { + return _.map(rawEntities, camelizeKeys); +}; /* We bundle all backend data constants in one big file, because the way we currently * export the Ruby values to JS is very inefficient (it basically spins up an entire server per file) @@ -12,10 +18,14 @@ import I18n from './i18n' */ // ----- COUNTRIES ----- +const fictionalCountryIds = <%= Country::FICTIVE_IDS.to_json.html_safe %>; + +const countryData = loadStaticData(<%= Country.all_raw.to_json.html_safe %>); +const realCountryData = countryData.filter((c) => !fictionalCountryIds.includes(c.id)); export const countries = { - byIso2: _.mapValues(<%= Country.all.index_by(&:iso2).to_json.html_safe %>, extendCountries), - real: _.map(<%= Country.real.to_json.html_safe %>, extendCountries), + byIso2: _.mapValues(_.keyBy(countryData, 'iso2'), extendCountries), + real: _.map(realCountryData, extendCountries), }; function extendCountries(country) { @@ -27,8 +37,13 @@ function extendCountries(country) { // ----- CONTINENTS ----- +const continentData = loadStaticData(<%= Continent.all_raw.to_json.html_safe %>); + +const fictionalContinentIds = <%= Continent::FICTIVE_IDS.to_json.html_safe %>; +const realContinents = continentData.filter((c) => !fictionalContinentIds.includes(c.id)); + export const continents = { - real: _.map(<%= Continent.real.to_json.html_safe %>, extendContinents), + real: _.map(realContinents, extendContinents), }; function extendContinents(continent){ @@ -63,12 +78,13 @@ function extendCurrenciesData(rawFormat) { // ----- FORMATS ----- +const formatsData = loadStaticData(<%= Format.all_raw.to_json.html_safe %>); + export const formats = { - byId: _.mapValues(<%= Format.all.index_by(&:id).to_json.html_safe %>, extendFormats), + byId: _.mapValues(_.keyBy(formatsData, 'id'), extendFormats), }; function extendFormats(rawFormat) { - rawFormat = _.mapKeys(rawFormat, (v, k) => _.camelCase(k)); return { ...rawFormat, name: I18n.t(`formats.${rawFormat.id}`), @@ -78,18 +94,19 @@ function extendFormats(rawFormat) { // ----- EVENTS ----- +const eventsData = loadStaticData(<%= Event.all_raw.to_json.html_safe %>); + export const events = { - official: _.map(<%= Event.official.to_json.html_safe %>, extendEvents), - byId: _.mapValues(<%= Event.all.index_by(&:id).to_json.html_safe %>, extendEvents), + official: _.map(_.filter(eventsData, 'isOfficial'), extendEvents), + byId: _.mapValues(_.keyBy(eventsData, 'id'), extendEvents), }; function extendEvents(rawEvent) { - rawEvent = _.mapKeys(rawEvent, (v, k) => _.camelCase(k)); return { ...rawEvent, name: I18n.t(`events.${rawEvent.id}`), formats() { - return rawEvent.formatIds.map(formatId => formats.byId[formatId]); + return this.formatIds.map(formatId => formats.byId[formatId]); }, recommendedFormat() { return this.formats()[0]; @@ -112,12 +129,15 @@ function extendGenders(gender) { // ----- ROUND TYPES ----- +const roundTypeData = loadStaticData(<%= RoundType.all_raw.to_json.html_safe %>); + export const roundTypes = { - byId: _.mapValues(<%= RoundType.all.index_by(&:id).to_json.html_safe %>, extendRoundTypes), + byId: _.mapValues(_.keyBy(roundTypeData, 'id'), extendRoundTypes), }; function extendRoundTypes(rawFormat) { - return _.mapKeys(rawFormat, (v, k) => _.camelCase(k)); + // Simple identity right now but we may want to add cool stuff in the future + return rawFormat; } // ----- TIMEZONES ----- @@ -142,7 +162,12 @@ export const competitionConstants = { competitionRecentDays: <%= Competition::RECENT_DAYS %>, }; -export const nonFutureCompetitionYears = <%= Competition.non_future_years.to_json %> +const currentYear = (new Date()).getFullYear(); +const yearsRange = _.range(2003, currentYear, 1); // range end is exclusive + +// Calling Ruby's `Competition.non_future_years` triggers a DB call, which we don't want. +// So we "fake" values by accepting that there was one competition in 1982 and then comps started in 2003 again. +export const nonFutureCompetitionYears = [1982, ...yearsRange]; // ----- RAILS ENV ----- @@ -158,7 +183,16 @@ export const competitionMaxShortNameLength = <%= Competition::MAX_CELL_NAME_LENG // ----- CHAMPIONSHIPS ----- -export const groupedChampionshipTypes = <%= Championship.grouped_championship_types.to_json.html_safe %> +const eligibleCountryForChampionshipData = loadStaticData(<%= EligibleCountryIso2ForChampionship.all_raw.to_json.html_safe %>); + +export const championshipTypeWorld = '<%= Championship::CHAMPIONSHIP_TYPE_WORLD %>' + +export const groupedChampionshipTypes = { + planetary: [championshipTypeWorld], + continental: _.map(_.sortBy(continents.real, 'name'), 'id'), + "multi-national": _.uniq(_.map(eligibleCountryForChampionshipData, 'championshipType')), + national: _.map(_.sortBy(countries.real, 'name'), 'iso2'), +} // ----- ROLES & GROUPS ----- diff --git a/db/migrate/20170517213035_update_list_of_countries.rb b/db/migrate/20170517213035_update_list_of_countries.rb index 3773563375..5c71d53e23 100644 --- a/db/migrate/20170517213035_update_list_of_countries.rb +++ b/db/migrate/20170517213035_update_list_of_countries.rb @@ -4,7 +4,7 @@ class UpdateListOfCountries < ActiveRecord::Migration[5.0] def up ActiveRecord::Base.transaction do Country.delete_all - Country::ALL_STATES.each(&:save!) + Country.load_json_data! # These substitions have been found by running the migration and checking # users with iso2 not matching anything in the 'Country' table. { diff --git a/db/migrate/20190803202212_update_wca_states.rb b/db/migrate/20190803202212_update_wca_states.rb index 0b4613321b..de9d2264dd 100644 --- a/db/migrate/20190803202212_update_wca_states.rb +++ b/db/migrate/20190803202212_update_wca_states.rb @@ -3,7 +3,7 @@ class UpdateWcaStates < ActiveRecord::Migration[5.2] def up Country.delete_all - Country::ALL_STATES.each(&:save!) + Country.load_json_data! # Extra changes due to some changes in country names models=[Person, Result, Competition] models.each do |m| @@ -14,7 +14,7 @@ def up def down Country.delete_all - Country::ALL_STATES.each(&:save!) + Country.load_json_data! models=[Person, Result, Competition] models.each do |m| m.where(countryId: "North Macedonia").update_all(countryId: "Macedonia") diff --git a/db/migrate/20200331082313_rename_some_wca_states.rb b/db/migrate/20200331082313_rename_some_wca_states.rb index 8eeba2bccf..ffa58cdd50 100644 --- a/db/migrate/20200331082313_rename_some_wca_states.rb +++ b/db/migrate/20200331082313_rename_some_wca_states.rb @@ -3,7 +3,7 @@ class RenameSomeWcaStates < ActiveRecord::Migration[5.2] def up Country.delete_all - Country::ALL_STATES.each(&:save!) + Country.load_json_data! # This change goes along renaming some WCA States (see https://github.com/thewca/wca-regulations/issues/962). models=[Person, Result, Competition] models.each do |m| @@ -14,7 +14,7 @@ def up def down Country.delete_all - Country::ALL_STATES.each(&:save!) + Country.load_json_data! models=[Person, Result, Competition] models.each do |m| m.where(countryId: "Vatican City").update_all(countryId: "Holy See") diff --git a/db/migrate/20240716200248_drop_championship_iso2_primary_key.rb b/db/migrate/20240716200248_drop_championship_iso2_primary_key.rb new file mode 100644 index 0000000000..f1284bb948 --- /dev/null +++ b/db/migrate/20240716200248_drop_championship_iso2_primary_key.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class DropChampionshipIso2PrimaryKey < ActiveRecord::Migration[7.1] + def change + remove_column :eligible_country_iso2s_for_championship, :id + end +end diff --git a/db/migrate/20240717123410_drop_cell_name_from_event.rb b/db/migrate/20240717123410_drop_cell_name_from_event.rb new file mode 100644 index 0000000000..599608de8a --- /dev/null +++ b/db/migrate/20240717123410_drop_cell_name_from_event.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class DropCellNameFromEvent < ActiveRecord::Migration[7.1] + def change + remove_column :Events, :cellName + end +end diff --git a/db/migrate/20240717142240_alter_external_registration_page_max_length.rb b/db/migrate/20240717142240_alter_external_registration_page_max_length.rb new file mode 100644 index 0000000000..0ef994d116 --- /dev/null +++ b/db/migrate/20240717142240_alter_external_registration_page_max_length.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AlterExternalRegistrationPageMaxLength < ActiveRecord::Migration[7.1] + def change + change_column :Competitions, :external_registration_page, :string, limit: 200 + end +end diff --git a/db/schema.rb b/db/schema.rb index a9db707726..1d428e4ab9 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_07_17_095605) do +ActiveRecord::Schema[7.1].define(version: 2024_07_17_142240) 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 @@ -57,7 +57,7 @@ t.boolean "qualification_results" t.text "qualification_results_reason" t.string "name_reason" - t.string "external_registration_page" + t.string "external_registration_page", limit: 200 t.datetime "confirmed_at", precision: nil t.boolean "event_restrictions" t.text "event_restrictions_reason" @@ -147,7 +147,6 @@ t.string "name", limit: 54, default: "", null: false t.integer "rank", default: 0, null: false t.string "format", limit: 10, default: "", null: false - t.string "cellName", limit: 45, default: "", null: false end create_table "Formats", id: { type: :string, limit: 1, default: "" }, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| @@ -709,7 +708,7 @@ t.index ["competition_id"], name: "index_delegate_reports_on_competition_id", unique: true end - create_table "eligible_country_iso2s_for_championship", charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| + create_table "eligible_country_iso2s_for_championship", id: false, charset: "utf8mb4", collation: "utf8mb4_unicode_ci", force: :cascade do |t| t.string "championship_type", null: false t.string "eligible_country_iso2", null: false t.index ["championship_type", "eligible_country_iso2"], name: "index_eligible_iso2s_for_championship_on_type_and_country_iso2", unique: true diff --git a/db/seeds/continents.seeds.rb b/db/seeds/continents.seeds.rb index a9fd9c6288..b2d3dba2bc 100644 --- a/db/seeds/continents.seeds.rb +++ b/db/seeds/continents.seeds.rb @@ -1,11 +1,3 @@ # frozen_string_literal: true -sql = "INSERT INTO `Continents` (`id`, `name`, `recordName`, `latitude`, `longitude`, `zoom`) VALUES -('_Multiple Continents', 'Multiple Continents', '', 0, 0, 1), -('_Africa', 'Africa', 'AfR', 213671, 16984850, 3), -('_Asia', 'Asia', 'AsR', 34364439, 108330700, 2), -('_Europe', 'Europe', 'ER', 58299984, 23049300, 3), -('_North America', 'North America', 'NAR', 45486546, -93449700, 3), -('_Oceania', 'Oceania', 'OcR', -25274398, 133775136, 3), -('_South America', 'South America', 'SAR', -21735104, -63281250, 3);" -ActiveRecord::Base.connection.execute(sql) +Continent.load_json_data! diff --git a/db/seeds/countries.seeds.rb b/db/seeds/countries.seeds.rb index ed8e12a111..111ea7679c 100644 --- a/db/seeds/countries.seeds.rb +++ b/db/seeds/countries.seeds.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -Country::ALL_STATES.each(&:save!) +Country.load_json_data! diff --git a/db/seeds/eligible_country_iso2s_for_championship.seeds.rb b/db/seeds/eligible_country_iso2s_for_championship.seeds.rb index 3530bcfaa2..eb47db7f42 100644 --- a/db/seeds/eligible_country_iso2s_for_championship.seeds.rb +++ b/db/seeds/eligible_country_iso2s_for_championship.seeds.rb @@ -1,11 +1,5 @@ # frozen_string_literal: true after :countries do - { - "greater_china" => ["China", "Hong Kong", "Macau", "Taiwan"], - }.each do |championship_type, country_names| - Country.where(name: country_names).pluck(:iso2).each do |iso2| - EligibleCountryIso2ForChampionship.create!(championship_type: championship_type, eligible_country_iso2: iso2) - end - end + EligibleCountryIso2ForChampionship.load_json_data! end diff --git a/db/seeds/events.seeds.rb b/db/seeds/events.seeds.rb index 26f90b840c..1d411424d9 100644 --- a/db/seeds/events.seeds.rb +++ b/db/seeds/events.seeds.rb @@ -1,26 +1,3 @@ # frozen_string_literal: true -sql = "INSERT INTO `Events` (`id`, `name`, `rank`, `format`, `cellName`) VALUES -('333', '3x3x3 Cube', 10, 'time', '3x3x3 Cube'), -('222', '2x2x2 Cube', 20, 'time', '2x2x2 Cube'), -('444', '4x4x4 Cube', 30, 'time', '4x4x4 Cube'), -('555', '5x5x5 Cube', 40, 'time', '5x5x5 Cube'), -('666', '6x6x6 Cube', 50, 'time', '6x6x6 Cube'), -('777', '7x7x7 Cube', 60, 'time', '7x7x7 Cube'), -('333bf', '3x3x3 Blindfolded', 70, 'time', '3x3x3 Blindfolded'), -('333fm', '3x3x3 Fewest Moves', 80, 'number', '3x3x3 Fewest Moves'), -('333oh', '3x3x3 One-Handed', 90, 'time', '3x3x3 One-Handed'), -('clock', 'Clock', 110, 'time', 'Clock'), -('minx', 'Megaminx', 120, 'time', 'Megaminx'), -('pyram', 'Pyraminx', 130, 'time', 'Pyraminx'), -('skewb', 'Skewb', 140, 'time', 'Skewb'), -('sq1', 'Square-1', 150, 'time', 'Square-1'), -('444bf', '4x4x4 Blindfolded', 160, 'time', '4x4x4 Blindfolded'), -('555bf', '5x5x5 Blindfolded', 170, 'time', '5x5x5 Blindfolded'), -('333mbf', '3x3x3 Multi-Blind', 180, 'multi', '3x3x3 Multi-Blind'), -('333ft', '3x3x3 With Feet', 996, 'time', '3x3x3 With Feet'), -('magic', 'Magic', 997, 'time', 'Magic'), -('mmagic', 'Master Magic', 998, 'time', 'Master Magic'), -('333mbo', '3x3x3 Multi-Blind Old Style', 999, 'multi', '3x3x3 Multi-Blind Old Style');" - -ActiveRecord::Base.connection.execute(sql) +Event.load_json_data! diff --git a/db/seeds/formats.seeds.rb b/db/seeds/formats.seeds.rb index e234127b78..2bff37cabb 100644 --- a/db/seeds/formats.seeds.rb +++ b/db/seeds/formats.seeds.rb @@ -1,12 +1,3 @@ # frozen_string_literal: true -sql = "INSERT INTO Formats -(id, name, sort_by, sort_by_second, expected_solve_count, -trim_fastest_n, trim_slowest_n) -VALUES -('1', 'Best of 1', 'single', 'average', 1, 0, 0), -('2', 'Best of 2', 'single', 'average', 2, 0, 0), -('3', 'Best of 3', 'single', 'average', 3, 0, 0), -('a', 'Average of 5', 'average', 'single', 5, 1, 1), -('m', 'Mean of 3', 'average', 'single', 3, 0, 0);" -ActiveRecord::Base.connection.execute(sql) +Format.load_json_data! diff --git a/db/seeds/preferred_formats.seeds.rb b/db/seeds/preferred_formats.seeds.rb index f65148dda1..8cc742337d 100644 --- a/db/seeds/preferred_formats.seeds.rb +++ b/db/seeds/preferred_formats.seeds.rb @@ -1,27 +1,3 @@ # frozen_string_literal: true -sql = "INSERT INTO preferred_formats -(event_id, format_id, ranking) -VALUES -('333', 'a', 1), -('222', 'a', 1), -('444', 'a', 1), -('555', 'a', 1), -('666', 'm', 1), -('777', 'm', 1), -('333bf', '3', 1), -('333fm', 'm', 1), ('333fm', '2', 2), ('333fm', '1', 3), -('333oh', 'a', 1), -('clock', 'a', 1), -('minx', 'a', 1), -('pyram', 'a', 1), -('skewb', 'a', 1), -('sq1', 'a', 1), -('444bf', '3', 1), -('555bf', '3', 1), -('333mbf', '1', 1), ('333mbf', '2', 2), ('333mbf', '3', 3), -('333ft', 'a', 1), ('333ft', '3', 2), ('333ft', '2', 3), ('333ft', '1', 4), -('magic', 'a', 1), -('mmagic', 'a', 1), -('333mbo', '3', 1), ('333mbo', '2', 2), ('333mbo', '1', 3);" -ActiveRecord::Base.connection.execute(sql) +PreferredFormat.load_json_data! diff --git a/db/seeds/round_types.seeds.rb b/db/seeds/round_types.seeds.rb index 83eea86413..094da15bd5 100644 --- a/db/seeds/round_types.seeds.rb +++ b/db/seeds/round_types.seeds.rb @@ -1,15 +1,3 @@ # frozen_string_literal: true -sql = "INSERT INTO `RoundTypes` (`id`, `rank`, `name`, `cellName`, `final`) VALUES -('0', 19, 'Qualification round', 'Qualification round', 0), -('1', 29, 'First round', 'First round', 0), -('2', 50, 'Second round', 'Second round', 0), -('3', 79, 'Semi Final', 'Semi Final', 0), -('b', 39, 'B Final', 'B Final', 0), -('c', 90, 'Final', 'Final', 1), -('d', 20, 'First round', 'First round', 0), -('e', 59, 'Second round', 'Second round', 0), -('f', 99, 'Final', 'Final', 1), -('g', 70, 'Semi Final', 'Semi Final', 0), -('h', 10, 'Qualification round', 'Qualification round', 0);" -ActiveRecord::Base.connection.execute(sql) +RoundType.load_json_data! diff --git a/lib/database_dumper.rb b/lib/database_dumper.rb index d2b8079357..fa2084f0ca 100644 --- a/lib/database_dumper.rb +++ b/lib/database_dumper.rb @@ -200,7 +200,6 @@ def self.actions_to_column_sanitizers(columns_by_action) column_sanitizers: actions_to_column_sanitizers( copy: %w( id - cellName format name rank @@ -790,7 +789,6 @@ def self.actions_to_column_sanitizers(columns_by_action) "eligible_country_iso2s_for_championship" => { column_sanitizers: actions_to_column_sanitizers( copy: %w( - id championship_type eligible_country_iso2 ), @@ -964,11 +962,14 @@ def self.actions_to_column_sanitizers(columns_by_action) column_sanitizers: actions_to_column_sanitizers( copy: %w( id - cellName format name rank ), + fake_values: { + # Copy over column to keep backwards compatibility + "cellName" => "name", + }, ), }.freeze, "Formats" => { diff --git a/lib/results_validators/individual_results_validator.rb b/lib/results_validators/individual_results_validator.rb index a1db83ece7..56058b91a4 100644 --- a/lib/results_validators/individual_results_validator.rb +++ b/lib/results_validators/individual_results_validator.rb @@ -159,7 +159,7 @@ def check_similar_results(context, index, results_for_round) def check_result_after_dns(context, all_solve_times) # Now let's try to find a DNS result followed by a non-DNS result - first_index = all_solve_times.find_index(SolveTime::DNS) + first_index = all_solve_times.find_index(&:dns?) # Just use '5' here to get all of them if first_index && all_solve_times[first_index, 5].any?(&:complete?) competition_id, result, round_id, = context diff --git a/lib/solve_time.rb b/lib/solve_time.rb index 2edb4b8c90..baf88621e5 100644 --- a/lib/solve_time.rb +++ b/lib/solve_time.rb @@ -277,9 +277,6 @@ def times_over_10_minutes_must_be_rounded end DNF_VALUE = -1 - DNF = SolveTime.new('333', nil, DNF_VALUE) DNS_VALUE = -2 - DNS = SolveTime.new('333', nil, DNS_VALUE) SKIPPED_VALUE = 0 - SKIPPED = SolveTime.new('333', nil, SKIPPED_VALUE) end diff --git a/lib/static_data/championship_eligible_iso2.json b/lib/static_data/championship_eligible_iso2.json new file mode 100644 index 0000000000..32fbfef1ef --- /dev/null +++ b/lib/static_data/championship_eligible_iso2.json @@ -0,0 +1,8 @@ +{ + "greater_china": [ + "CN", + "HK", + "MO", + "TW" + ] +} \ No newline at end of file diff --git a/lib/static_data/continents.json b/lib/static_data/continents.json new file mode 100644 index 0000000000..fd05ec273a --- /dev/null +++ b/lib/static_data/continents.json @@ -0,0 +1,58 @@ +[ + { + "id": "_Africa", + "name": "Africa", + "recordName": "AfR", + "latitude": 213671, + "longitude": 16984850, + "zoom": 3 + }, + { + "id": "_Asia", + "name": "Asia", + "recordName": "AsR", + "latitude": 34364439, + "longitude": 108330700, + "zoom": 2 + }, + { + "id": "_Europe", + "name": "Europe", + "recordName": "ER", + "latitude": 58299984, + "longitude": 23049300, + "zoom": 3 + }, + { + "id": "_Multiple Continents", + "name": "Multiple Continents", + "recordName": "", + "latitude": 0, + "longitude": 0, + "zoom": 1 + }, + { + "id": "_North America", + "name": "North America", + "recordName": "NAR", + "latitude": 45486546, + "longitude": -93449700, + "zoom": 3 + }, + { + "id": "_Oceania", + "name": "Oceania", + "recordName": "OcR", + "latitude": -25274398, + "longitude": 133775136, + "zoom": 3 + }, + { + "id": "_South America", + "name": "South America", + "recordName": "SAR", + "latitude": -21735104, + "longitude": -63281250, + "zoom": 3 + } +] \ No newline at end of file diff --git a/lib/static_data/countries.fictive.json b/lib/static_data/countries.fictive.json new file mode 100644 index 0000000000..1b2e4e1a5d --- /dev/null +++ b/lib/static_data/countries.fictive.json @@ -0,0 +1,50 @@ +[ + { + "id": "XA", + "name": "Multiple Countries (Asia)", + "continentId": "_Asia", + "iso2": "XA" + }, + { + "id": "XE", + "name": "Multiple Countries (Europe)", + "continentId": "_Europe", + "iso2": "XE" + }, + { + "id": "XF", + "name": "Multiple Countries (Africa)", + "continentId": "_Africa", + "iso2": "XF" + }, + { + "id": "XM", + "name": "Multiple Countries (Americas)", + "continentId": "_Multiple Continents", + "iso2": "XM" + }, + { + "id": "XN", + "name": "Multiple Countries (North America)", + "continentId": "_North America", + "iso2": "XN" + }, + { + "id": "XO", + "name": "Multiple Countries (Oceania)", + "continentId": "_Oceania", + "iso2": "XO" + }, + { + "id": "XS", + "name": "Multiple Countries (South America)", + "continentId": "_South America", + "iso2": "XS" + }, + { + "id": "XW", + "name": "Multiple Countries (World)", + "continentId": "_Multiple Continents", + "iso2": "XW" + } +] \ No newline at end of file diff --git a/lib/static_data/countries.real.json b/lib/static_data/countries.real.json new file mode 120000 index 0000000000..8b8c8ee692 --- /dev/null +++ b/lib/static_data/countries.real.json @@ -0,0 +1 @@ +../../config/wca-states.json \ No newline at end of file diff --git a/lib/static_data/events.json b/lib/static_data/events.json new file mode 100644 index 0000000000..cbe0f79e81 --- /dev/null +++ b/lib/static_data/events.json @@ -0,0 +1,326 @@ +[ + { + "id": "333", + "rank": 10, + "format": "time", + "name": "3x3x3 Cube", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "222", + "rank": 20, + "format": "time", + "name": "2x2x2 Cube", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "444", + "rank": 30, + "format": "time", + "name": "4x4x4 Cube", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "555", + "rank": 40, + "format": "time", + "name": "5x5x5 Cube", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "666", + "rank": 50, + "format": "time", + "name": "6x6x6 Cube", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "m" + ] + }, + { + "id": "777", + "rank": 60, + "format": "time", + "name": "7x7x7 Cube", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "m" + ] + }, + { + "id": "333bf", + "rank": 70, + "format": "time", + "name": "3x3x3 Blindfolded", + "can_change_time_limit": true, + "can_have_cutoff": false, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "3" + ] + }, + { + "id": "333fm", + "rank": 80, + "format": "number", + "name": "3x3x3 Fewest Moves", + "can_change_time_limit": false, + "can_have_cutoff": true, + "is_timed_event": false, + "is_fewest_moves": true, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "m", + "2", + "1" + ] + }, + { + "id": "333oh", + "rank": 90, + "format": "time", + "name": "3x3x3 One-Handed", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "clock", + "rank": 110, + "format": "time", + "name": "Clock", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "minx", + "rank": 120, + "format": "time", + "name": "Megaminx", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "pyram", + "rank": 130, + "format": "time", + "name": "Pyraminx", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "skewb", + "rank": 140, + "format": "time", + "name": "Skewb", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "sq1", + "rank": 150, + "format": "time", + "name": "Square-1", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "a" + ] + }, + { + "id": "444bf", + "rank": 160, + "format": "time", + "name": "4x4x4 Blindfolded", + "can_change_time_limit": true, + "can_have_cutoff": false, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "3" + ] + }, + { + "id": "555bf", + "rank": 170, + "format": "time", + "name": "5x5x5 Blindfolded", + "can_change_time_limit": true, + "can_have_cutoff": false, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": true, + "format_ids": [ + "3" + ] + }, + { + "id": "333mbf", + "rank": 180, + "format": "multi", + "name": "3x3x3 Multi-Blind", + "can_change_time_limit": false, + "can_have_cutoff": true, + "is_timed_event": false, + "is_fewest_moves": false, + "is_multiple_blindfolded": true, + "is_official": true, + "format_ids": [ + "1", + "2", + "3" + ] + }, + { + "id": "333ft", + "rank": 996, + "format": "time", + "name": "3x3x3 With Feet", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": false, + "format_ids": [ + "a", + "3", + "2", + "1" + ] + }, + { + "id": "magic", + "rank": 997, + "format": "time", + "name": "Magic", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": false, + "format_ids": [ + "a" + ] + }, + { + "id": "mmagic", + "rank": 998, + "format": "time", + "name": "Master Magic", + "can_change_time_limit": true, + "can_have_cutoff": true, + "is_timed_event": true, + "is_fewest_moves": false, + "is_multiple_blindfolded": false, + "is_official": false, + "format_ids": [ + "a" + ] + }, + { + "id": "333mbo", + "rank": 999, + "format": "multi", + "name": "3x3x3 Multi-Blind Old Style", + "can_change_time_limit": false, + "can_have_cutoff": true, + "is_timed_event": false, + "is_fewest_moves": false, + "is_multiple_blindfolded": true, + "is_official": false, + "format_ids": [ + "3", + "2", + "1" + ] + } +] \ No newline at end of file diff --git a/lib/static_data/formats.json b/lib/static_data/formats.json new file mode 100644 index 0000000000..9f8ea1d3b6 --- /dev/null +++ b/lib/static_data/formats.json @@ -0,0 +1,69 @@ +[ + { + "id": "1", + "sort_by": "single", + "sort_by_second": "average", + "expected_solve_count": 1, + "trim_fastest_n": 0, + "trim_slowest_n": 0, + "name": "Best of 1", + "short_name": "Bo1", + "allowed_first_phase_formats": [ + + ] + }, + { + "id": "2", + "sort_by": "single", + "sort_by_second": "average", + "expected_solve_count": 2, + "trim_fastest_n": 0, + "trim_slowest_n": 0, + "name": "Best of 2", + "short_name": "Bo2", + "allowed_first_phase_formats": [ + "1" + ] + }, + { + "id": "3", + "sort_by": "single", + "sort_by_second": "average", + "expected_solve_count": 3, + "trim_fastest_n": 0, + "trim_slowest_n": 0, + "name": "Best of 3", + "short_name": "Bo3", + "allowed_first_phase_formats": [ + "1", + "2" + ] + }, + { + "id": "a", + "sort_by": "average", + "sort_by_second": "single", + "expected_solve_count": 5, + "trim_fastest_n": 1, + "trim_slowest_n": 1, + "name": "Average of 5", + "short_name": "Ao5", + "allowed_first_phase_formats": [ + "2" + ] + }, + { + "id": "m", + "sort_by": "average", + "sort_by_second": "single", + "expected_solve_count": 3, + "trim_fastest_n": 0, + "trim_slowest_n": 0, + "name": "Mean of 3", + "short_name": "Mo3", + "allowed_first_phase_formats": [ + "1", + "2" + ] + } +] \ No newline at end of file diff --git a/lib/static_data/preferred_formats.json b/lib/static_data/preferred_formats.json new file mode 100644 index 0000000000..99e7307f19 --- /dev/null +++ b/lib/static_data/preferred_formats.json @@ -0,0 +1,57 @@ +{ + "333": [ + "a" + ], + "222": [ + "a" + ], + "444": [ + "a" + ], + "555": [ + "a" + ], + "666": [ + "m" + ], + "777": [ + "m" + ], + "333bf": [ + "3" + ], + "333fm": [ + "m", + "2", + "1" + ], + "333oh": [ + "a" + ], + "clock": [ + "a" + ], + "minx": [ + "a" + ], + "pyram": [ + "a" + ], + "skewb": [ + "a" + ], + "sq1": [ + "a" + ], + "444bf": [ + "3" + ], + "555bf": [ + "3" + ], + "333mbf": [ + "1", + "2", + "3" + ] +} \ No newline at end of file diff --git a/lib/static_data/round_types.json b/lib/static_data/round_types.json new file mode 100644 index 0000000000..da898a7c2f --- /dev/null +++ b/lib/static_data/round_types.json @@ -0,0 +1,79 @@ +[ + { + "id": "0", + "rank": 19, + "name": "Qualification round", + "cellName": "Qualification round", + "final": false + }, + { + "id": "1", + "rank": 29, + "name": "First round", + "cellName": "First round", + "final": false + }, + { + "id": "2", + "rank": 50, + "name": "Second round", + "cellName": "Second round", + "final": false + }, + { + "id": "3", + "rank": 79, + "name": "Semi Final", + "cellName": "Semi Final", + "final": false + }, + { + "id": "b", + "rank": 39, + "name": "B Final", + "cellName": "B Final", + "final": false + }, + { + "id": "c", + "rank": 90, + "name": "Final", + "cellName": "Final", + "final": true + }, + { + "id": "d", + "rank": 20, + "name": "First round", + "cellName": "First round", + "final": false + }, + { + "id": "e", + "rank": 59, + "name": "Second round", + "cellName": "Second round", + "final": false + }, + { + "id": "f", + "rank": 99, + "name": "Final", + "cellName": "Final", + "final": true + }, + { + "id": "g", + "rank": 70, + "name": "Semi Final", + "cellName": "Semi Final", + "final": false + }, + { + "id": "h", + "rank": 10, + "name": "Qualification round", + "cellName": "Qualification round", + "final": false + } +] \ No newline at end of file diff --git a/lib/tasks/static_data.rake b/lib/tasks/static_data.rake new file mode 100644 index 0000000000..dbbff771fd --- /dev/null +++ b/lib/tasks/static_data.rake @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +namespace :static_data do + desc 'Import static data from JSON files under lib/static_data into the database' + task load_json: :environment do + EligibleCountryIso2ForChampionship.load_json_data! + Continent.load_json_data! + Country.load_json_data! + Event.load_json_data! + Format.load_json_data! + PreferredFormat.load_json_data! + RoundType.load_json_data! + end + + desc 'Dump static data from the database into JSON files under lib/static_data' + task dump_json: :environment do + EligibleCountryIso2ForChampionship.write_json_data! + Continent.write_json_data! + Country.write_json_data! + Event.write_json_data! + Format.write_json_data! + PreferredFormat.write_json_data! + RoundType.write_json_data! + end +end + +if Rails.env.production? && Rake::Task.task_defined?("db:prepare") + Rake::Task["db:prepare"].enhance do + Rake::Task["static_data:load_json"].invoke + end +end diff --git a/spec/i18n_spec.rb b/spec/i18n_spec.rb index 64a9668361..fdd3361f29 100644 --- a/spec/i18n_spec.rb +++ b/spec/i18n_spec.rb @@ -31,7 +31,7 @@ end it "English country translations, database names, all match the names in wca-states.json" do - Country::WCA_STATES["states_lists"].map do |list| + Country::WCA_STATES_JSON["states_lists"].map do |list| list["states"].map do |state| state_id = state["id"] || I18n.transliterate(state["name"]).tr("'", "_") country = Country.c_find(state_id) diff --git a/spec/lib/solve_time_spec.rb b/spec/lib/solve_time_spec.rb index 4a5593436b..49625ac90b 100644 --- a/spec/lib/solve_time_spec.rb +++ b/spec/lib/solve_time_spec.rb @@ -40,18 +40,18 @@ def solve_time(centis) end it "treats skipped as worst" do - expect(SolveTime::SKIPPED > SolveTime::DNF).to eq true - expect(SolveTime::SKIPPED > SolveTime::DNS).to eq true - expect(SolveTime::SKIPPED > solve_time(30)).to eq true + expect(solve_time(SolveTime::SKIPPED_VALUE) > solve_time(SolveTime::DNF_VALUE)).to eq true + expect(solve_time(SolveTime::SKIPPED_VALUE) > solve_time(SolveTime::DNS_VALUE)).to eq true + expect(solve_time(SolveTime::SKIPPED_VALUE) > solve_time(30)).to eq true end it "treats DNS as worse than DNF" do - expect(SolveTime::DNS > SolveTime::DNF).to eq true - expect(SolveTime::DNS > solve_time(30)).to eq true + expect(solve_time(SolveTime::DNS_VALUE) > solve_time(SolveTime::DNF_VALUE)).to eq true + expect(solve_time(SolveTime::DNS_VALUE) > solve_time(30)).to eq true end it "treats DNS as worse than a finished solve" do - expect(SolveTime::DNF > solve_time(30)).to eq true + expect(solve_time(SolveTime::DNF_VALUE) > solve_time(30)).to eq true end end diff --git a/spec/models/competition_spec.rb b/spec/models/competition_spec.rb index cb66be8b2e..aaebe8b181 100644 --- a/spec/models/competition_spec.rb +++ b/spec/models/competition_spec.rb @@ -1580,20 +1580,4 @@ def change_and_check_activities(new_start_date, new_end_date) expect(new_competition).not_to be_valid end end - - context 'validations defined appropriately' do - it 'all string column names appear in one of the validation lists' do - string_columns = Competition.columns.select { |col| col.type == :string }.map(&:name) - all_listed_columns = Competition::VALIDATE_STRING_LENGTH + Competition::DONT_VALIDATE_STRING_LENGTH - - string_columns.each do |column| - expect(all_listed_columns).to include(column) - end - end - - it 'no string column name appears in both validation lists' do - common_columns = Competition::VALIDATE_STRING_LENGTH & Competition::DONT_VALIDATE_STRING_LENGTH - expect(common_columns).to be_empty - end - end end diff --git a/spec/models/light_result_spec.rb b/spec/models/light_result_spec.rb index fd6f14af7c..989ac17cab 100644 --- a/spec/models/light_result_spec.rb +++ b/spec/models/light_result_spec.rb @@ -41,7 +41,7 @@ def solve_time(centis) result = build_result "eventId" => "333", "value1" => 20, "value2" => 10, "value3" => 60, "value4" => SolveTime::SKIPPED_VALUE, "value5" => SolveTime::SKIPPED_VALUE, "average" => SolveTime::SKIPPED_VALUE, "formatId" => "a" expect(result.format.expected_solve_count).to eq 5 expect(result.solve_times).to eq [ - solve_time(20), solve_time(10), solve_time(60), SolveTime::SKIPPED, SolveTime::SKIPPED + solve_time(20), solve_time(10), solve_time(60), solve_time(SolveTime::SKIPPED_VALUE), solve_time(SolveTime::SKIPPED_VALUE) ] end @@ -49,7 +49,7 @@ def solve_time(centis) result = build_result "eventId" => "333", "value1" => 20, "value2" => 10, "value3" => 60, "value4" => SolveTime::SKIPPED_VALUE, "value5" => SolveTime::SKIPPED_VALUE, "average" => SolveTime::SKIPPED_VALUE, "formatId" => "3" expect(result.format.expected_solve_count).to eq 3 expect(result.solve_times).to eq [ - solve_time(20), solve_time(10), solve_time(60), SolveTime::SKIPPED, SolveTime::SKIPPED + solve_time(20), solve_time(10), solve_time(60), solve_time(SolveTime::SKIPPED_VALUE), solve_time(SolveTime::SKIPPED_VALUE) ] end end diff --git a/spec/models/result_spec.rb b/spec/models/result_spec.rb index ffe5bd8d6e..65b086f51b 100644 --- a/spec/models/result_spec.rb +++ b/spec/models/result_spec.rb @@ -362,21 +362,6 @@ end end - context "333ft" do - let(:formatId) { "3" } - let(:eventId) { "333ft" } - let!(:round) { FactoryBot.create(:round, competition: competition, event_id: "333ft", format_id: "3") } - - it "does compute average" do - result = build_result(value1: 999, value2: 1000, value3: 1001, value4: 0, value5: 0, best: 999, average: 1000) - expect(result).to be_valid - - result.average = 33 - expect(result.compute_correct_average).to eq 1000 - expect(result).to be_invalid_with_errors(average: ["should be 1000"]) - end - end - context "333mbf" do let(:formatId) { "3" } let(:eventId) { "333mbf" }