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" }