diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..0a77011
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "editor.tabSize": 2
+}
\ No newline at end of file
diff --git a/app/controllers/worlds_controller.rb b/app/controllers/worlds_controller.rb
new file mode 100644
index 0000000..e5dba40
--- /dev/null
+++ b/app/controllers/worlds_controller.rb
@@ -0,0 +1,135 @@
+class WorldsController < ApplicationController
+ allow_unauthenticated_access
+
+ before_action :set_title
+ before_action :set_fr_lang
+
+ def set_title
+ @title = I18n.t("worlds.title")
+ @nonav = true
+ end
+
+ def set_fr_lang
+ I18n.locale = "fr"
+ end
+
+ def main
+ authorize :world_bet
+
+ @world_bets_w = WorldBet.all.order(world_score: :desc)
+ @world_bets_f = WorldBet.all.order(french_score: :desc)
+ end
+
+ def about
+ authorize :world_bet
+ end
+
+ def collab
+ authorize :world_bet
+
+ participant_ids = Set[]
+ @world_bets = WorldBet.all.each do |b|
+ b.bet.split(";", -1).first.split(",", -1).each do |participant_id|
+ participant_ids.add(participant_id)
+ end
+ b.bet.split(";", -1).last.split(",", -1).each do |participant_id|
+ participant_ids.add(participant_id)
+ end
+ end
+
+ @participants = WorldParticipant.includes([ :world_country ]).find(participant_ids.to_a).sort_by { |p| p.name }
+ end
+
+ def view
+ authorize :world_bet
+
+ @world_bet = WorldBet.find(params[:bet_id])
+ @french_participants = WorldParticipant.includes([ :world_country ]).find(@world_bet.bet.split(";", -1).last.split(",", -1).map { |x| Integer(x) })
+ @world_participants = WorldParticipant.includes([ :world_country ]).find(@world_bet.bet.split(";", -1).first.split(",", -1).map { |x| Integer(x) })
+ end
+
+ def new
+ authorize :world_bet
+
+ @world_bet = WorldBet.new
+ @world_participants = WorldParticipant.includes([ :world_country ]).all.order(:name)
+ france = WorldCountry.where(code: "FR").first
+ @french_participants = WorldParticipant.includes([ :world_country ]).where(world_country: france).order(:name)
+ end
+
+ def create
+ authorize :world_bet
+
+
+ @world_bet = WorldBet.new(world_bet_params)
+ @world_bet.score = 0
+ @world_bet.french_score = 0
+ @world_bet.world_score = 0
+ all_params = params.require(:world_bet)
+
+ w_set = Set[]
+ f_set = Set[]
+ @w_array = []
+ @f_array = []
+ (1..10).each do |i|
+ if all_params.key?("w_bet_#{i}")
+ p_id = all_params["w_bet_#{i}"]
+ w_set.add(p_id)
+ @w_array.push(p_id)
+ end
+ if all_params.key?("f_bet_#{i}")
+ p_id = all_params["f_bet_#{i}"]
+ f_set.add(p_id)
+ @f_array.push(p_id)
+ end
+ end
+ @world_bet.serialize_bet(@w_array, @f_array)
+
+ if w_set.length != 10 || f_set.length != 10
+ @world_bet.errors.add(:name, I18n.t("activerecord.errors.models.world_bet.attributes.bet.duplicate"))
+ end
+
+ if w_set.length == 10 && f_set.length == 10 && @world_bet.save
+ redirect_to "/wjpc_bets/main", notice: t("worlds.new.notice")
+ else
+ @world_participants = WorldParticipant.all.sort_by { |p| p.name }
+ france = WorldCountry.where(code: "FR").first
+ @french_participants = WorldParticipant.where(world_country: france).sort_by { |p| p.name }
+ render :new, status: :unprocessable_entity
+ end
+ end
+
+ def update
+ authorize :world_bet
+
+ @participant = WorldParticipant.find(params[:participant_id])
+ if @participant.update(world_participant_params)
+ participants_hash = {}
+ WorldParticipant.all.each do |p|
+ if p.score != nil
+ participants_hash[p.id] = p.score
+ end
+ end
+ WorldBet.all.each do |b|
+ old_score = b.score
+ b.compute_score(participants_hash)
+ if b.score != old_score
+ b.save
+ end
+ end
+ redirect_back(fallback_location: root_path, notice: t("worlds.collab.notice"))
+ else
+ redirect_to "/wjpc_bets/collab", notice: t("worlds.collab.error")
+ end
+ end
+
+ private
+
+ def world_bet_params
+ params.expect(world_bet: [ :name ])
+ end
+
+ def world_participant_params
+ params.expect(world_participant: [ :qualifier_result, :semifinal_result, :final_result ])
+ end
+end
diff --git a/app/helpers/worlds_helper.rb b/app/helpers/worlds_helper.rb
new file mode 100644
index 0000000..a1429c0
--- /dev/null
+++ b/app/helpers/worlds_helper.rb
@@ -0,0 +1,2 @@
+module WorldsHelper
+end
diff --git a/app/lib/flags.rb b/app/lib/flags.rb
new file mode 100644
index 0000000..184b563
--- /dev/null
+++ b/app/lib/flags.rb
@@ -0,0 +1,261 @@
+module Flags
+ FLAGS = {
+ AD: "๐ฆ๐ฉ",
+ AE: "๐ฆ๐ช",
+ AF: "๐ฆ๐ซ",
+ AG: "๐ฆ๐ฌ",
+ AI: "๐ฆ๐ฎ",
+ AL: "๐ฆ๐ฑ",
+ AM: "๐ฆ๐ฒ",
+ AO: "๐ฆ๐ด",
+ AQ: "๐ฆ๐ถ",
+ AR: "๐ฆ๐ท",
+ AS: "๐ฆ๐ธ",
+ AT: "๐ฆ๐น",
+ AU: "๐ฆ๐บ",
+ AW: "๐ฆ๐ผ",
+ AX: "๐ฆ๐ฝ",
+ AZ: "๐ฆ๐ฟ",
+ BA: "๐ง๐ฆ",
+ BB: "๐ง๐ง",
+ BD: "๐ง๐ฉ",
+ BE: "๐ง๐ช",
+ BF: "๐ง๐ซ",
+ BG: "๐ง๐ฌ",
+ BH: "๐ง๐ญ",
+ BI: "๐ง๐ฎ",
+ BJ: "๐ง๐ฏ",
+ BL: "๐ง๐ฑ",
+ BM: "๐ง๐ฒ",
+ BN: "๐ง๐ณ",
+ BO: "๐ง๐ด",
+ BQ: "๐ง๐ถ",
+ BR: "๐ง๐ท",
+ BS: "๐ง๐ธ",
+ BT: "๐ง๐น",
+ BV: "๐ง๐ป",
+ BW: "๐ง๐ผ",
+ BY: "๐ง๐พ",
+ BZ: "๐ง๐ฟ",
+ CA: "๐จ๐ฆ",
+ CC: "๐จ๐จ",
+ CD: "๐จ๐ฉ",
+ CF: "๐จ๐ซ",
+ CG: "๐จ๐ฌ",
+ CH: "๐จ๐ญ",
+ CI: "๐จ๐ฎ",
+ CK: "๐จ๐ฐ",
+ CL: "๐จ๐ฑ",
+ CM: "๐จ๐ฒ",
+ CN: "๐จ๐ณ",
+ CO: "๐จ๐ด",
+ CR: "๐จ๐ท",
+ CU: "๐จ๐บ",
+ CV: "๐จ๐ป",
+ CW: "๐จ๐ผ",
+ CX: "๐จ๐ฝ",
+ CY: "๐จ๐พ",
+ CZ: "๐จ๐ฟ",
+ DE: "๐ฉ๐ช",
+ DJ: "๐ฉ๐ฏ",
+ DK: "๐ฉ๐ฐ",
+ DM: "๐ฉ๐ฒ",
+ DO: "๐ฉ๐ด",
+ DZ: "๐ฉ๐ฟ",
+ EC: "๐ช๐จ",
+ EE: "๐ช๐ช",
+ EG: "๐ช๐ฌ",
+ EH: "๐ช๐ญ",
+ ER: "๐ช๐ท",
+ ES: "๐ช๐ธ",
+ ET: "๐ช๐น",
+ FI: "๐ซ๐ฎ",
+ FJ: "๐ซ๐ฏ",
+ FK: "๐ซ๐ฐ",
+ FM: "๐ซ๐ฒ",
+ FO: "๐ซ๐ด",
+ FR: "๐ซ๐ท",
+ GA: "๐ฌ๐ฆ",
+ GB: "๐ฌ๐ง",
+ GD: "๐ฌ๐ฉ",
+ GE: "๐ฌ๐ช",
+ GF: "๐ฌ๐ซ",
+ GG: "๐ฌ๐ฌ",
+ GH: "๐ฌ๐ญ",
+ GI: "๐ฌ๐ฎ",
+ GL: "๐ฌ๐ฑ",
+ GM: "๐ฌ๐ฒ",
+ GN: "๐ฌ๐ณ",
+ GP: "๐ฌ๐ต",
+ GQ: "๐ฌ๐ถ",
+ GR: "๐ฌ๐ท",
+ GS: "๐ฌ๐ธ",
+ GT: "๐ฌ๐น",
+ GU: "๐ฌ๐บ",
+ GW: "๐ฌ๐ผ",
+ GY: "๐ฌ๐พ",
+ HK: "๐ญ๐ฐ",
+ HM: "๐ญ๐ฒ",
+ HN: "๐ญ๐ณ",
+ HR: "๐ญ๐ท",
+ HT: "๐ญ๐น",
+ HU: "๐ญ๐บ",
+ ID: "๐ฎ๐ฉ",
+ IE: "๐ฎ๐ช",
+ IL: "๐ฎ๐ฑ",
+ IM: "๐ฎ๐ฒ",
+ IN: "๐ฎ๐ณ",
+ IO: "๐ฎ๐ด",
+ IQ: "๐ฎ๐ถ",
+ IR: "๐ฎ๐ท",
+ IS: "๐ฎ๐ธ",
+ IT: "๐ฎ๐น",
+ JE: "๐ฏ๐ช",
+ JM: "๐ฏ๐ฒ",
+ JO: "๐ฏ๐ด",
+ JP: "๐ฏ๐ต",
+ KE: "๐ฐ๐ช",
+ KG: "๐ฐ๐ฌ",
+ KH: "๐ฐ๐ญ",
+ KI: "๐ฐ๐ฎ",
+ KM: "๐ฐ๐ฒ",
+ KN: "๐ฐ๐ณ",
+ KP: "๐ฐ๐ต",
+ KR: "๐ฐ๐ท",
+ KW: "๐ฐ๐ผ",
+ KY: "๐ฐ๐พ",
+ KZ: "๐ฐ๐ฟ",
+ LA: "๐ฑ๐ฆ",
+ LB: "๐ฑ๐ง",
+ LC: "๐ฑ๐จ",
+ LI: "๐ฑ๐ฎ",
+ LK: "๐ฑ๐ฐ",
+ LR: "๐ฑ๐ท",
+ LS: "๐ฑ๐ธ",
+ LT: "๐ฑ๐น",
+ LU: "๐ฑ๐บ",
+ LV: "๐ฑ๐ป",
+ LY: "๐ฑ๐พ",
+ MA: "๐ฒ๐ฆ",
+ MC: "๐ฒ๐จ",
+ MD: "๐ฒ๐ฉ",
+ ME: "๐ฒ๐ช",
+ MF: "๐ฒ๐ซ",
+ MG: "๐ฒ๐ฌ",
+ MH: "๐ฒ๐ญ",
+ MK: "๐ฒ๐ฐ",
+ ML: "๐ฒ๐ฑ",
+ MM: "๐ฒ๐ฒ",
+ MN: "๐ฒ๐ณ",
+ MO: "๐ฒ๐ด",
+ MP: "๐ฒ๐ต",
+ MQ: "๐ฒ๐ถ",
+ MR: "๐ฒ๐ท",
+ MS: "๐ฒ๐ธ",
+ MT: "๐ฒ๐น",
+ MU: "๐ฒ๐บ",
+ MV: "๐ฒ๐ป",
+ MW: "๐ฒ๐ผ",
+ MX: "๐ฒ๐ฝ",
+ MY: "๐ฒ๐พ",
+ MZ: "๐ฒ๐ฟ",
+ NA: "๐ณ๐ฆ",
+ NC: "๐ณ๐จ",
+ NE: "๐ณ๐ช",
+ NF: "๐ณ๐ซ",
+ NG: "๐ณ๐ฌ",
+ NI: "๐ณ๐ฎ",
+ NL: "๐ณ๐ฑ",
+ NO: "๐ณ๐ด",
+ NP: "๐ณ๐ต",
+ NR: "๐ณ๐ท",
+ NU: "๐ณ๐บ",
+ NZ: "๐ณ๐ฟ",
+ OM: "๐ด๐ฒ",
+ PA: "๐ต๐ฆ",
+ PE: "๐ต๐ช",
+ PF: "๐ต๐ซ",
+ PG: "๐ต๐ฌ",
+ PH: "๐ต๐ญ",
+ PK: "๐ต๐ฐ",
+ PL: "๐ต๐ฑ",
+ PM: "๐ต๐ฒ",
+ PN: "๐ต๐ณ",
+ PR: "๐ต๐ท",
+ PS: "๐ต๐ธ",
+ PT: "๐ต๐น",
+ PW: "๐ต๐ผ",
+ PY: "๐ต๐พ",
+ QA: "๐ถ๐ฆ",
+ RE: "๐ท๐ช",
+ RO: "๐ท๐ด",
+ RS: "๐ท๐ธ",
+ RU: "๐ท๐บ",
+ RW: "๐ท๐ผ",
+ SA: "๐ธ๐ฆ",
+ SB: "๐ธ๐ง",
+ SC: "๐ธ๐จ",
+ SD: "๐ธ๐ฉ",
+ SE: "๐ธ๐ช",
+ SG: "๐ธ๐ฌ",
+ SH: "๐ธ๐ญ",
+ SI: "๐ธ๐ฎ",
+ SJ: "๐ธ๐ฏ",
+ SK: "๐ธ๐ฐ",
+ SL: "๐ธ๐ฑ",
+ SM: "๐ธ๐ฒ",
+ SN: "๐ธ๐ณ",
+ SO: "๐ธ๐ด",
+ SR: "๐ธ๐ท",
+ SS: "๐ธ๐ธ",
+ ST: "๐ธ๐น",
+ SV: "๐ธ๐ป",
+ SX: "๐ธ๐ฝ",
+ SY: "๐ธ๐พ",
+ SZ: "๐ธ๐ฟ",
+ TC: "๐น๐จ",
+ TD: "๐น๐ฉ",
+ TF: "๐น๐ซ",
+ TG: "๐น๐ฌ",
+ TH: "๐น๐ญ",
+ TJ: "๐น๐ฏ",
+ TK: "๐น๐ฐ",
+ TL: "๐น๐ฑ",
+ TM: "๐น๐ฒ",
+ TN: "๐น๐ณ",
+ TO: "๐น๐ด",
+ TR: "๐น๐ท",
+ TT: "๐น๐น",
+ TV: "๐น๐ป",
+ TW: "๐น๐ผ",
+ TZ: "๐น๐ฟ",
+ UA: "๐บ๐ฆ",
+ UG: "๐บ๐ฌ",
+ UM: "๐บ๐ฒ",
+ US: "๐บ๐ธ",
+ UY: "๐บ๐พ",
+ UZ: "๐บ๐ฟ",
+ VA: "๐ป๐ฆ",
+ VC: "๐ป๐จ",
+ VE: "๐ป๐ช",
+ VG: "๐ป๐ฌ",
+ VI: "๐ป๐ฎ",
+ VN: "๐ป๐ณ",
+ VU: "๐ป๐บ",
+ WF: "๐ผ๐ซ",
+ WS: "๐ผ๐ธ",
+ YE: "๐พ๐ช",
+ YT: "๐พ๐น",
+ ZA: "๐ฟ๐ฆ",
+ ZM: "๐ฟ๐ฒ",
+ ZW: "๐ฟ๐ผ"
+ }
+
+ def self.get_flag(code)
+ sym_code = code.to_sym
+ if FLAGS.has_key?(sym_code)
+ return FLAGS[sym_code]
+ end
+ ""
+ end
+end
diff --git a/app/models/world_bet.rb b/app/models/world_bet.rb
new file mode 100644
index 0000000..2d498a4
--- /dev/null
+++ b/app/models/world_bet.rb
@@ -0,0 +1,93 @@
+# == Schema Information
+#
+# Table name: world_bets
+#
+# id :integer not null, primary key
+# bet :string
+# french_score :integer
+# name :string
+# score :integer
+# world_score :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+class WorldBet < ApplicationRecord
+ validates :name, presence: true, uniqueness: true
+ validates :score, presence: true, numericality: { greater_than_or_equal_to: 0 }
+ validates :french_score, presence: true, numericality: { greater_than_or_equal_to: 0 }
+ validates :world_score, presence: true, numericality: { greater_than_or_equal_to: 0 }
+
+ @@multiplier_array = [ 20, 15, 12, 10, 8, 6, 4, 3, 2, 1 ]
+ @@points_array = [ 100, 70, 50, 40, 30, 26, 23, 20, 17, 15 ]
+
+ def self.multiplier(i)
+ if i < 0 or i > 9
+ return 0
+ end
+ @@multiplier_array[i]
+ end
+
+ def self.points(i)
+ if !i || i < 1
+ return 0
+ end
+ if i <= 10
+ return @@points_array[i - 1]
+ end
+ if i <= 20
+ return 12
+ end
+ if i <= 30
+ return 10
+ end
+ if i <= 40
+ return 8
+ end
+ if i <= 50
+ return 7
+ end
+ if i <= 60
+ return 6
+ end
+ if i <= 70
+ return 5
+ end
+ if i <= 80
+ return 4
+ end
+ if i <= 90
+ return 3
+ end
+ if i <= 100
+ return 2
+ end
+ if i <= 150
+ return 1
+ end
+ 0
+ end
+
+ def serialize_bet(w, f)
+ self.bet = "#{w.join(',')};#{f.join(',')}"
+ end
+
+ def compute_score(participant_scores)
+ self.score = 0
+ self.french_score = 0
+ self.world_score = 0
+ w = self.bet.split(";", -1).first.split(",", -1)
+ w.each_with_index do |participant_id, i|
+ if participant_scores.has_key?(Integer(participant_id))
+ self.score += participant_scores[Integer(participant_id)] * WorldBet.multiplier(i)
+ self.world_score += participant_scores[Integer(participant_id)] * WorldBet.multiplier(i)
+ end
+ end
+ f = self.bet.split(";", -1).last.split(",", -1)
+ f.each_with_index do |participant_id, i|
+ if participant_scores.has_key?(Integer(participant_id))
+ self.score += participant_scores[Integer(participant_id)] * WorldBet.multiplier(i)
+ self.french_score += participant_scores[Integer(participant_id)] * WorldBet.multiplier(i)
+ end
+ end
+ end
+end
diff --git a/app/models/world_country.rb b/app/models/world_country.rb
new file mode 100644
index 0000000..cf7ea8f
--- /dev/null
+++ b/app/models/world_country.rb
@@ -0,0 +1,24 @@
+# == Schema Information
+#
+# Table name: world_countries
+#
+# id :integer not null, primary key
+# code :string not null
+# name :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+class WorldCountry < ApplicationRecord
+ has_many :world_participants
+
+ validates :code, presence: true, uniqueness: true
+ validates :name, presence: true, uniqueness: true
+
+ def self.add_countries(str)
+ str.split(";", -1).each do |c|
+ name = c.split(",", -1).first
+ code = c.split(",", -1).last
+ WorldCountry.create(name: name, code: code)
+ end
+ end
+end
diff --git a/app/models/world_participant.rb b/app/models/world_participant.rb
new file mode 100644
index 0000000..d515d47
--- /dev/null
+++ b/app/models/world_participant.rb
@@ -0,0 +1,56 @@
+# == Schema Information
+#
+# Table name: world_participants
+#
+# id :integer not null, primary key
+# final_result :integer
+# name :string not null
+# qualifier_result :integer
+# round :string not null
+# score :integer
+# semifinal_result :integer
+# username :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# world_country_id :integer not null
+#
+# Indexes
+#
+# index_world_participants_on_world_country_id (world_country_id)
+#
+# Foreign Keys
+#
+# world_country_id (world_country_id => world_countries.id)
+#
+class WorldParticipant < ApplicationRecord
+ belongs_to :world_country
+
+ validates :name, presence: true
+ validates :username, presence: true
+ validates :round, inclusion: { in: %w[a b c d], message: "%{value} is not a valid round" }
+ validates :qualifier_result, numericality: { greater_than_or_equal_to: 1 }, if: -> { qualifier_result.present? }
+ validates :semifinal_result, numericality: { greater_than_or_equal_to: 1 }, if: -> { semifinal_result.present? }
+ validates :final_result, numericality: { greater_than_or_equal_to: 1 }, if: -> { final_result.present? }
+
+ before_save :compute_score
+
+ def compute_score
+ self.score = WorldBet.points(qualifier_result) + WorldBet.points(semifinal_result) * 2 + WorldBet.points(final_result) * 4
+ end
+
+ def self.add_participants(str)
+ str.split(";", -1).each do |c|
+ cs = c.split(",", -1)
+ if cs.length >= 4
+ name = cs.first
+ username = cs[1]
+ country_code = cs[2]
+ round = cs.last
+ country = WorldCountry.where(code: country_code).first
+ if country
+ WorldParticipant.create(name: name, username: username, world_country: country, round: round)
+ end
+ end
+ end
+ end
+end
diff --git a/app/policies/world_bet_policy.rb b/app/policies/world_bet_policy.rb
new file mode 100644
index 0000000..dadef9a
--- /dev/null
+++ b/app/policies/world_bet_policy.rb
@@ -0,0 +1,29 @@
+class WorldBetPolicy < ApplicationPolicy
+ def main?
+ true
+ end
+
+ def about?
+ true
+ end
+
+ def new?
+ true
+ end
+
+ def create?
+ true
+ end
+
+ def collab?
+ true
+ end
+
+ def update?
+ true
+ end
+
+ def view?
+ true
+ end
+end
diff --git a/app/views/application/_worlds_nav.html.slim b/app/views/application/_worlds_nav.html.slim
new file mode 100644
index 0000000..357d496
--- /dev/null
+++ b/app/views/application/_worlds_nav.html.slim
@@ -0,0 +1,15 @@
+.row
+ .col
+ ul.nav.nav-tabs.mb-4
+ li.nav-item
+ a.nav-link class=active_page("/wjpc_bets/main") href="/wjpc_bets/main"
+ = t("worlds.main.tab")
+ li.nav-item
+ a.nav-link class=active_page("/wjpc_bets/about") href="/wjpc_bets/about"
+ = t("worlds.about.tab")
+ li.nav-item
+ a.nav-link class=active_page("/wjpc_bets/collaborative_filling") href="/wjpc_bets/collaborative_filling"
+ = t("worlds.collab.tab")
+ li.nav-item
+ a.nav-link class=active_page("/wjpc_bets/new") href="/wjpc_bets/new"
+ = t("worlds.new.tab")
\ No newline at end of file
diff --git a/app/views/worlds/about.html.slim b/app/views/worlds/about.html.slim
new file mode 100644
index 0000000..2ab988c
--- /dev/null
+++ b/app/views/worlds/about.html.slim
@@ -0,0 +1,83 @@
+= render "worlds_nav"
+
+.alert.alert-warning role="alert"
+ = t("worlds.about.one_bet_message")
+
+.alert.alert-primary role="alert"
+ = t("worlds.about.details_message")
+ .mt-3
+ = t("worlds.about.two_rankings_message")
+ .mt-3
+ = t("worlds.about.collaborative_filling_message")
+
+.row.mt-4
+ .col
+ table.table.table-striped.table-hover
+ thead
+ tr
+ th = t("worlds.about.ranking")
+ th = t("worlds.about.qualifier_round")
+ th = t("worlds.about.semifinal_round")
+ th = t("worlds.about.finals_round")
+ tbody
+ - (1..10).each do |i|
+ tr
+ td #{i}
+ td #{WorldBet.points(i)}
+ td #{WorldBet.points(i) * 2}
+ td #{WorldBet.points(i) * 4}
+ tr
+ td 11-20
+ td #{WorldBet.points(20)}
+ td #{WorldBet.points(20) * 2}
+ td #{WorldBet.points(20) * 4}
+ tr
+ td 21-30
+ td #{WorldBet.points(30)}
+ td #{WorldBet.points(30) * 2}
+ td #{WorldBet.points(30) * 4}
+ tr
+ td 31-40
+ td #{WorldBet.points(40)}
+ td #{WorldBet.points(40) * 2}
+ td #{WorldBet.points(40) * 4}
+ tr
+ td 41-50
+ td #{WorldBet.points(50)}
+ td #{WorldBet.points(50) * 2}
+ td #{WorldBet.points(50) * 4}
+ tr
+ td 51-60
+ td #{WorldBet.points(60)}
+ td #{WorldBet.points(60) * 2}
+ td #{WorldBet.points(60) * 4}
+ tr
+ td 61-70
+ td #{WorldBet.points(70)}
+ td #{WorldBet.points(70) * 2}
+ td #{WorldBet.points(70) * 4}
+ tr
+ td 71-80
+ td #{WorldBet.points(80)}
+ td #{WorldBet.points(80) * 2}
+ td #{WorldBet.points(80) * 4}
+ tr
+ td 81-90
+ td #{WorldBet.points(90)}
+ td #{WorldBet.points(90) * 2}
+ td #{WorldBet.points(90) * 4}
+ tr
+ td 91-100
+ td #{WorldBet.points(100)}
+ td #{WorldBet.points(100) * 2}
+ td #{WorldBet.points(100) * 4}
+ tr
+ td 101-150
+ td #{WorldBet.points(150)}
+ td #{WorldBet.points(150) * 2}
+ td #{WorldBet.points(150) * 4}
+ tr
+ td 151+
+ td #{WorldBet.points(151)}
+ td #{WorldBet.points(151) * 2}
+ td #{WorldBet.points(151) * 4}
\ No newline at end of file
diff --git a/app/views/worlds/collab.html.slim b/app/views/worlds/collab.html.slim
new file mode 100644
index 0000000..e26d6b2
--- /dev/null
+++ b/app/views/worlds/collab.html.slim
@@ -0,0 +1,131 @@
+= render "worlds_nav"
+
+.alert.alert-primary role="alert"
+ = t("worlds.collab.details_message")
+ .mt-3
+ = t("worlds.collab.error_message")
+
+.alert.alert-warning role="alert"
+ = t("worlds.collab.rank_message")
+
+h4.mt-4 Round A
+
+- @participants.each do |p|
+ - if p.round == 'a'
+ .row.mt-3
+ .col
+ = form_with model: WorldParticipant, url: "/wjpc_bets/update/#{p.id}", method: :patch do |form|
+ .input-group
+ .input-group-text style="width: 350px;" #{Flags.get_flag(p.world_country.code) + " " + p.name}
+ .input-group-text = t("worlds.main.rank")
+ = form.text_field :qualifier_result, value: p.qualifier_result, autocomplete: "off", class: "form-control", style: "color: orange; max-width: 60px;"
+ .input-group-text = t("worlds.main.points")
+ .input-group-text style="color: green; width: 60px;" #{WorldBet.points(p.qualifier_result)}
+ = form.submit t("helpers.buttons.validate"), class: "btn btn-primary", onclick: "updateScrollPos()"
+
+javascript:
+ function updateScrollPos() {
+ wsy = window.scrollY;
+ setTimeout(() => window.scrollTo(0, wsy), 25);
+ setTimeout(() => window.scrollTo(0, wsy), 50);
+ setTimeout(() => window.scrollTo(0, wsy), 75);
+ setTimeout(() => window.scrollTo(0, wsy), 100);
+ setTimeout(() => window.scrollTo(0, wsy), 125);
+ setTimeout(() => window.scrollTo(0, wsy), 150);
+ setTimeout(() => window.scrollTo(0, wsy), 175);
+ setTimeout(() => window.scrollTo(0, wsy), 200);
+ setTimeout(() => window.scrollTo(0, wsy), 225);
+ setTimeout(() => window.scrollTo(0, wsy), 250);
+ }
+
+
+h4.mt-5 Round B
+
+- @participants.each do |p|
+ - if p.round == 'b'
+ .row.mt-3
+ .col
+ = form_with model: WorldParticipant, url: "/wjpc_bets/update/#{p.id}", method: :patch do |form|
+ .input-group
+ .input-group-text style="width: 350px;" #{Flags.get_flag(p.world_country.code) + " " + p.name}
+ .input-group-text = t("worlds.main.rank")
+ = form.text_field :qualifier_result, value: p.qualifier_result, autocomplete: "off", class: "form-control", style: "color: orange; max-width: 60px;"
+ .input-group-text = t("worlds.main.points")
+ .input-group-text style="color: green; width: 60px;" #{WorldBet.points(p.qualifier_result)}
+ = form.submit t("helpers.buttons.validate"), class: "btn btn-primary", onclick: "updateScrollPos()"
+
+h4.mt-5 Round C
+
+- @participants.each do |p|
+ - if p.round == 'c'
+ .row.mt-3
+ .col
+ = form_with model: WorldParticipant, url: "/wjpc_bets/update/#{p.id}", method: :patch do |form|
+ .input-group
+ .input-group-text style="width: 350px;" #{Flags.get_flag(p.world_country.code) + " " + p.name}
+ .input-group-text = t("worlds.main.rank")
+ = form.text_field :qualifier_result, value: p.qualifier_result, autocomplete: "off", class: "form-control", style: "color: orange; max-width: 60px;"
+ .input-group-text = t("worlds.main.points")
+ .input-group-text style="color: green; width: 60px;" #{WorldBet.points(p.qualifier_result)}
+ = form.submit t("helpers.buttons.validate"), class: "btn btn-primary", onclick: "updateScrollPos()"
+
+h4.mt-5 Round D
+
+- @participants.each do |p|
+ - if p.round == 'd'
+ .row.mt-3
+ .col
+ = form_with model: WorldParticipant, url: "/wjpc_bets/update/#{p.id}", method: :patch do |form|
+ .input-group
+ .input-group-text style="width: 350px;" #{Flags.get_flag(p.world_country.code) + " " + p.name}
+ .input-group-text = t("worlds.main.rank")
+ = form.text_field :qualifier_result, value: p.qualifier_result, autocomplete: "off", class: "form-control", style: "color: orange; max-width: 60px;"
+ .input-group-text = t("worlds.main.points")
+ .input-group-text style="color: green; width: 60px;" #{WorldBet.points(p.qualifier_result)}
+ = form.submit t("helpers.buttons.validate"), class: "btn btn-primary", onclick: "updateScrollPos()"
+
+h4.mt-5 = "#{t("worlds.collab.semifinal")} 1"
+
+- @participants.each do |p|
+ - if p.round == 'a' || p.round == 'b'
+ .row.mt-3
+ .col
+ = form_with model: WorldParticipant, url: "/wjpc_bets/update/#{p.id}", method: :patch do |form|
+ .input-group
+ .input-group-text style="width: 350px;" #{Flags.get_flag(p.world_country.code) + " " + p.name}
+ .input-group-text = t("worlds.main.rank")
+ = form.text_field :semifinal_result, value: p.semifinal_result, autocomplete: "off", class: "form-control", style: "color: orange; max-width: 60px;"
+ .input-group-text = t("worlds.main.points")
+ .input-group-text style="color: green; width: 60px;" #{WorldBet.points(p.semifinal_result) * 2}
+ = form.submit t("helpers.buttons.validate"), class: "btn btn-primary", onclick: "updateScrollPos()"
+
+h4.mt-5 = "#{t("worlds.collab.semifinal")} 2"
+
+- @participants.each do |p|
+ - if p.round == 'c' || p.round == 'd'
+ .row.mt-3
+ .col
+ = form_with model: WorldParticipant, url: "/wjpc_bets/update/#{p.id}", method: :patch do |form|
+ .input-group
+ .input-group-text style="width: 350px;" #{Flags.get_flag(p.world_country.code) + " " + p.name}
+ .input-group-text = t("worlds.main.rank")
+ = form.text_field :semifinal_result, value: p.semifinal_result, autocomplete: "off", class: "form-control", style: "color: orange; max-width: 60px;"
+ .input-group-text = t("worlds.main.points")
+ .input-group-text style="color: green; width: 60px;" #{WorldBet.points(p.semifinal_result) * 2}
+ = form.submit t("helpers.buttons.validate"), class: "btn btn-primary", onclick: "updateScrollPos()"
+
+h4.mt-5 = "#{t("worlds.collab.final")}"
+
+- @participants.each do |p|
+ .row.mt-3
+ .col
+ = form_with model: WorldParticipant, url: "/wjpc_bets/update/#{p.id}", method: :patch do |form|
+ .input-group
+ .input-group-text style="width: 350px;" #{Flags.get_flag(p.world_country.code) + " " + p.name}
+ .input-group-text = t("worlds.main.rank")
+ = form.text_field :final_result, value: p.final_result, autocomplete: "off", class: "form-control", style: "color: orange; max-width: 60px;"
+ .input-group-text = t("worlds.main.points")
+ .input-group-text style="color: green; width: 60px;" #{WorldBet.points(p.final_result) * 4}
+ = form.submit t("helpers.buttons.validate"), class: "btn btn-primary", onclick: "updateScrollPos()"
+
+.mb-5
diff --git a/app/views/worlds/main.html.slim b/app/views/worlds/main.html.slim
new file mode 100644
index 0000000..f69799b
--- /dev/null
+++ b/app/views/worlds/main.html.slim
@@ -0,0 +1,52 @@
+= render "worlds_nav"
+
+.row.mt-3
+ .col-6
+ h4 = t("worlds.main.world_participants")
+ table.table.table-striped.table-hover
+ thead
+ tr
+ th = t("worlds.main.rank")
+ th = t("worlds.main.name")
+ th = t("worlds.main.score")
+ tbody
+ - @world_bets_w.each_with_index do |b, i|
+ tr
+ - if i == 0
+ td ๐ฅ
+ - elsif i == 1
+ td ๐ฅ
+ - elsif i == 2
+ td ๐ฅ
+ - else
+ td #{i + 1}
+ td #{b.name}
+ td #{b.world_score}
+ td
+ a.btn.btn-sm.btn-secondary href="/wjpc_bets/view/#{b.id}"
+ = t("worlds.main.view_bet")
+
+ .col-6
+ h4 = t("worlds.main.french_participants")
+ table.table.table-striped.table-hover
+ thead
+ tr
+ th = t("worlds.main.rank")
+ th = t("worlds.main.name")
+ th = t("worlds.main.score")
+ tbody
+ - @world_bets_f.each_with_index do |b, i|
+ tr
+ - if i == 0
+ td ๐ฅ
+ - elsif i == 1
+ td ๐ฅ
+ - elsif i == 2
+ td ๐ฅ
+ - else
+ td #{i + 1}
+ td #{b.name}
+ td #{b.french_score}
+ td
+ a.btn.btn-sm.btn-secondary href="/wjpc_bets/view/#{b.id}"
+ = t("worlds.main.view_bet")
\ No newline at end of file
diff --git a/app/views/worlds/new.html.slim b/app/views/worlds/new.html.slim
new file mode 100644
index 0000000..581d06d
--- /dev/null
+++ b/app/views/worlds/new.html.slim
@@ -0,0 +1,45 @@
+= render "worlds_nav"
+
+= form_with model: @world_bet, url: "/wjpc_bets/new", method: :post do |form|
+ .alert.alert-primary role="alert"
+ = t("worlds.new.message")
+
+ .row.mt-4
+ .col
+ .form-floating
+ = form.text_field :name, autocomplete: "off", class: "form-control"
+ = form.label :name, class: "required"
+
+ .row.mt-4
+ .col
+ h4 = t("worlds.new.world_section")
+
+ - (1..10).each do |i|
+ .row.mt-3
+ .col
+ .input-group
+ .input-group-text ##{i} (x#{WorldBet.multiplier(i - 1)})
+ = form.select "w_bet_#{i}", @world_participants.map { |p| [Flags.get_flag(p.world_country.code) + " " + p.name, p.id] }, {}, class: "form-select"
+
+ .row.mt-4
+ .col
+ h4 = t("worlds.new.french_section")
+
+ - (1..10).each do |i|
+ .row.mt-3
+ .col
+ .input-group
+ .input-group-text ##{i} (x#{WorldBet.multiplier(i - 1)})
+ = form.select "f_bet_#{i}", @french_participants.map { |p| [Flags.get_flag(p.world_country.code) + " " + p.name, p.id] }, {}, class: "form-select"
+
+ - if @w_array && @f_array
+ - (1..10).each do |i|
+ javascript:
+ el = document.querySelector('select[name="world_bet[w_bet_#{i}]"]');
+ el.value = "#{@w_array[i - 1]}"
+ el = document.querySelector('select[name="world_bet[f_bet_#{i}]"]');
+ el.value = "#{@f_array[i - 1]}"
+
+ .row.mt-4.mb-5
+ .col
+ = form.submit t("helpers.buttons.validate"), class: "btn btn-primary"
\ No newline at end of file
diff --git a/app/views/worlds/view.html.slim b/app/views/worlds/view.html.slim
new file mode 100644
index 0000000..e688be9
--- /dev/null
+++ b/app/views/worlds/view.html.slim
@@ -0,0 +1,49 @@
+= render "worlds_nav"
+
+h4.mt-3 = t("worlds.view.subtitle", name: @world_bet.name).html_safe
+
+.row.mt-3
+ .col
+ h5 = t("worlds.view.world_participants")
+ table.table.table-striped.table-hover
+ thead
+ tr
+ th = t("worlds.view.participant")
+ th = t("worlds.view.multiplier")
+ th Q
+ th S
+ th F
+ th = t("worlds.view.total")
+ tbody
+ - @world_participants.each_with_index do |p, i|
+ tr
+ td = Flags.get_flag(p.world_country.code) + " " + p.name
+ td style="color: cadetblue;"
+ = "x#{WorldBet.multiplier(i)}"
+ td = "#{p.qualifier_result || "?"} > #{WorldBet.points(p.qualifier_result)}p".html_safe
+ td = "#{p.semifinal_result || "?"} > #{WorldBet.points(p.semifinal_result) * 2}p".html_safe
+ td = "#{p.final_result || "?"} > #{WorldBet.points(p.final_result) * 4}p".html_safe
+ td = "#{p.score || 0} x #{WorldBet.multiplier(i)} = #{(p.score || 0) * WorldBet.multiplier(i)}p".html_safe
+
+.row.mt-3
+ .col
+ h5 = t("worlds.view.french_participants")
+ table.table.table-striped.table-hover
+ thead
+ tr
+ th = t("worlds.view.participant")
+ th = t("worlds.view.multiplier")
+ th Q
+ th S
+ th F
+ th = t("worlds.view.total")
+ tbody
+ - @french_participants.each_with_index do |p, i|
+ tr
+ td = Flags.get_flag(p.world_country.code) + " " + p.name
+ td style="color: cadetblue;"
+ = "x#{WorldBet.multiplier(i)}"
+ td = "#{p.qualifier_result || "?"} > #{WorldBet.points(p.qualifier_result)}p".html_safe
+ td = "#{p.semifinal_result || "?"} > #{WorldBet.points(p.semifinal_result) * 2}p".html_safe
+ td = "#{p.final_result || "?"} > #{WorldBet.points(p.final_result) * 4}p".html_safe
+ td = "#{p.score || 0} x #{WorldBet.multiplier(i)} = #{(p.score || 0) * WorldBet.multiplier(i)}p".html_safe
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a831e85..932017b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -113,6 +113,8 @@ en:
email_address: Email address
lang: Language
password: New password
+ world_bet:
+ name: Name
errors:
models:
completion:
@@ -182,6 +184,13 @@ en:
taken: This username is already taken
password:
blank: Your password cannot be empty
+ world_bet:
+ attributes:
+ bet:
+ duplicate: You can't select a participant twice in either of the two selection
+ name:
+ blank: You must provide a name
+ taken: This name is already taken
categories:
destroy:
notice: Category deleted
@@ -420,3 +429,47 @@ en:
new:
notice: User created
title: "New user"
+ worlds:
+ about:
+ collaborative_filling_message: The ranking is updated collaboratively, see the dedicated tab for more details.
+ details_message: To make a bet you'll have to select some contestants (see the dedicated tab for more details on that, and multipliers). You'll score points when they do well in the competition, the table below gives the amount of points you get depending on their ranking on each round, from qualifiers to the finals. This however only accounts for the solo category.
+ final_round: Finals
+ one_bet_message: Only one bet is allowed per person, please do not overflow the system with bets.
+ points: Points
+ qualifier_round: Qualifier round
+ ranking: Ranking
+ semifinal_round: Semifinals
+ tab: Rules
+ two_rankings_message: There are two rankings, one for the bet over all participants, and one for the bet over French participants.
+ collab:
+ details_message: Here you will all be able to collaboratively keep up-to-date the ranks of the participants that were selectec by bets. Anyone is able to make any update here, I trust you to do it with care, not cheating, and correcting errors if you see any. I'm doing it this way, so you don't have to wait for me to have the time to update everything :)
+ error: An error occured, the rank must be a numerical value in the range [1, 250]
+ error_message: If a participant did not qualify, do not fill their ranks for the subsequent rounds. If a rank was filled for them by mistake, please change it to any value above 150 so its score remains 0.
+ final: Final
+ notice: Participant rank updated
+ rank_message: Below should be indicated the *rank* reached by each participant on each round, not the points. Points are automatically calculated.
+ semifinal: Semifinal
+ tab: Collaborative filling
+ main:
+ french_participants: French participants
+ name: Name
+ points: Points
+ rank: Rank
+ score: Score
+ tab: Rankings
+ view_bet: View bet
+ world_participants: All participants
+ new:
+ french_section: Bet over French participants
+ message: To make a bet, you have to provide two ordered lists of 10 participants, one among all participants, and the other one only among French participants. The order matters! A score multiplier will be applied to each selected contestant (in parenthesis below). You can't select a participant twice on a list, but of course you can totally put a French contestant in both lists ;)
+ notice: The bet has been made!
+ tab: Make a bet!
+ world_section: Bet over all participants
+ title: WJPC bets
+ view:
+ french_participants: On French participants
+ multiplier: Multiplier
+ participant: Participant
+ subtitle: "%{name}'s bet"
+ total: Total
+ world_participants: On all participants
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 662137e..ea00d94 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -84,6 +84,8 @@ fr:
email_address: Adresse email
lang: Langue de l'interface
password: Nouveau mot de passe
+ world_bet:
+ name: Nom
errors:
models:
completion:
@@ -153,6 +155,14 @@ fr:
taken: Ce nom d'utilisateur.ice est dรฉjร utilisรฉ
password:
blank: Le mot de passe ne peut pas รชtre vide
+ world_bet:
+ attributes:
+ bet:
+ duplicate:
+ Il n'est pas possible de choisir un.e participant.e plusieurs fois dans une des deux listes
+ name:
+ blank: Le nom est obligatoire
+ taken: Ce nom est dรฉjร pris
categories:
destroy:
notice: Catรฉgorie supprimรฉe
@@ -391,3 +401,48 @@ fr:
new:
notice: Utilisateur.ice ajoutรฉ.e
title: "Nouveau.elle utilisateur.ice"
+ worlds:
+ about:
+ collaborative_filling_message: Les rรฉsultats des participant.e.s sont mis ร jour de maniรจre collaborative, plus d'infos dans l'onglet dรฉdiรฉ.
+ details_message: Pour faire un pari, tu devras choisir plusieurs participant.e.s (plus d'infos sur comment รงa marche et sur les facteurs multiplicatifs dans l'onglet dรฉdiรฉ). Tu marqueras des points en fonction de comment iels se classent dans chaque round, depuis les qualifs jusqu'ร la finale (catรฉgorie solo uniquement). La table ci-dessous donne la rรฉpartition des points en fonction des classements.
+ final_round: Finale
+ one_bet_message: Un seul pari par personne, ne surchargez pas le systรจme svp.
+ points: Points
+ qualifier_round: Qualifications
+ ranking: Classement
+ semifinal_round: Demi-finales
+ tab: Rรจgles
+ two_rankings_message: "Il y a deux classements : un sur les paris parmi l'ensemble des participant.e.s, et l'autres sur les paris sur les franรงais.e.s."
+ collab:
+ details_message: Ici est l'endroit pour mettre librement et collaborativement ร jour les rรฉsultats des participant.e.s sรฉlectionnรฉ.e.s dans les paris. Je mets cet espace ร disposition pour permettre un remplissage plus rapide sans avoir besoin d'attendre que j'ai le temps de tout remplir moi-mรชme. Je vous fais confiance pour faire รงa avec soin, sans tricher, et en corrigeant des erreurs si vous en trouvez :)
+ error: "Une erreur est survenue : vรฉrifie que tu as bien rentrรฉ un rang dans l'intervalle [1, 250]"
+ error_message: Si une participant.e ne se qualifie pas lors d'un round, pas besoin alors de remplir son rang pour les rounds qui suivent. Si un rang a รฉtรฉ rempli par erreur, changez-le pour n'importe quel rang au-dessus de 150 histoire que รงa revienne ร un score de 0.
+ final: Finale
+ notice: "Le rang de cet.te participant.e a รฉtรฉ mis ร jour"
+ rank_message: Il s'agit ici d'indiquer les *classements* obtenus par les participant.e.s lors des diffรฉrents rounds. Les points seront ensuite calculรฉs automatiquement.
+ semifinal: Semifinal
+ tab: Remplisage collaboratif
+ main:
+ french_participants: Sur les participant.e.s franรงais.e.s
+ name: Nom
+ points: Points
+ rank: Rang
+ score: Score
+ tab: Classements
+ view_bet: Voir le pari
+ world_participants: Sur toutes les participant.e.s
+ new:
+ french_section: Pari sur les participant.e.s franรงais.e.s
+ message: Pour faire un pari, tu dois choisir 10 participant.e.s sur 2 listes, une comprenant l'ensemble des participant.e.s, l'autre comprenant uniquement les participant.e.s franรงais.e.s. L'ordre a son importance ! Un facteur multiplicatif sera appliquรฉ sur chaque participant.e que tu auras sรฉlectionnรฉ.e (en parenthรจses ci-dessous). Tu ne peux pas choisir un.e mรชme participant.e plusieurs fois dans une liste, mais tu peux en revanche tout ร fait choisir un.e participant.e franรงais.e dans les deux listes !
+ notice: Le pari est enregistrรฉ !
+ tab: Fais ton pari !
+ title: Nouveau pari
+ world_section: Pari sur l'ensemble des participant.e.s
+ title: Les paris des WJPC
+ view:
+ french_participants: Sur les franรงais.e.s
+ multiplier: Multiplicateur
+ participant: Participant.e
+ subtitle: "Le pari de %{name}"
+ total: Total
+ world_participants: Sur tout le monde
diff --git a/config/routes.rb b/config/routes.rb
index 89a863c..3e416d2 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -82,4 +82,13 @@ Rails.application.routes.draw do
direct :offline_form_completed do |contest, offline|
"/public/#{contest.friendly_id}/offline/#{offline.generate_token_for(:token)}/completed"
end
+
+ get "wjpc_bets", to: redirect("/wjpc_bets/main")
+ get "wjpc_bets/main", to: "worlds#main"
+ get "wjpc_bets/about", to: "worlds#about"
+ get "wjpc_bets/collaborative_filling", to: "worlds#collab"
+ get "wjpc_bets/new", to: "worlds#new"
+ post "wjpc_bets/new", to: "worlds#create"
+ patch "wjpc_bets/update/:participant_id", to: "worlds#update"
+ get "wjpc_bets/view/:bet_id", to: "worlds#view"
end
diff --git a/db/migrate/20260904063514_create_world_countries.rb b/db/migrate/20260904063514_create_world_countries.rb
new file mode 100644
index 0000000..25a715a
--- /dev/null
+++ b/db/migrate/20260904063514_create_world_countries.rb
@@ -0,0 +1,10 @@
+class CreateWorldCountries < ActiveRecord::Migration[8.0]
+ def change
+ create_table :world_countries do |t|
+ t.string :name, null: false
+ t.string :code, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20260904063926_create_world_participants.rb b/db/migrate/20260904063926_create_world_participants.rb
new file mode 100644
index 0000000..5f0db26
--- /dev/null
+++ b/db/migrate/20260904063926_create_world_participants.rb
@@ -0,0 +1,14 @@
+class CreateWorldParticipants < ActiveRecord::Migration[8.0]
+ def change
+ create_table :world_participants do |t|
+ t.string :username, null: false
+ t.string :name, null: false
+ t.belongs_to :world_country, null: false, foreign_key: true
+ t.integer :qualifier_result
+ t.integer :semifinal_result
+ t.integer :final_result
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20260904065915_add_round_to_world_participant.rb b/db/migrate/20260904065915_add_round_to_world_participant.rb
new file mode 100644
index 0000000..f01a366
--- /dev/null
+++ b/db/migrate/20260904065915_add_round_to_world_participant.rb
@@ -0,0 +1,5 @@
+class AddRoundToWorldParticipant < ActiveRecord::Migration[8.0]
+ def change
+ add_column :world_participants, :round, :string, null: false
+ end
+end
diff --git a/db/migrate/20260904131402_create_world_bets.rb b/db/migrate/20260904131402_create_world_bets.rb
new file mode 100644
index 0000000..5d11ef6
--- /dev/null
+++ b/db/migrate/20260904131402_create_world_bets.rb
@@ -0,0 +1,11 @@
+class CreateWorldBets < ActiveRecord::Migration[8.0]
+ def change
+ create_table :world_bets do |t|
+ t.string :name
+ t.string :bet
+ t.integer :score
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20260904132641_add_score_to_world_participant.rb b/db/migrate/20260904132641_add_score_to_world_participant.rb
new file mode 100644
index 0000000..590aae8
--- /dev/null
+++ b/db/migrate/20260904132641_add_score_to_world_participant.rb
@@ -0,0 +1,5 @@
+class AddScoreToWorldParticipant < ActiveRecord::Migration[8.0]
+ def change
+ add_column :world_participants, :score, :integer
+ end
+end
diff --git a/db/migrate/20260905120054_add_world_score_to_world_bet.rb b/db/migrate/20260905120054_add_world_score_to_world_bet.rb
new file mode 100644
index 0000000..4a24fff
--- /dev/null
+++ b/db/migrate/20260905120054_add_world_score_to_world_bet.rb
@@ -0,0 +1,5 @@
+class AddWorldScoreToWorldBet < ActiveRecord::Migration[8.0]
+ def change
+ add_column :world_bets, :world_score, :integer
+ end
+end
diff --git a/db/migrate/20260905120106_add_french_score_to_world_bet.rb b/db/migrate/20260905120106_add_french_score_to_world_bet.rb
new file mode 100644
index 0000000..897ca0f
--- /dev/null
+++ b/db/migrate/20260905120106_add_french_score_to_world_bet.rb
@@ -0,0 +1,5 @@
+class AddFrenchScoreToWorldBet < ActiveRecord::Migration[8.0]
+ def change
+ add_column :world_bets, :french_score, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c0eef81..9fe7250 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[8.0].define(version: 2026_08_25_071844) do
+ActiveRecord::Schema[8.0].define(version: 2026_09_05_120106) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@@ -203,6 +203,37 @@ ActiveRecord::Schema[8.0].define(version: 2026_08_25_071844) do
t.index ["email_address"], name: "index_users_on_email_address", unique: true
end
+ create_table "world_bets", force: :cascade do |t|
+ t.string "name"
+ t.string "bet"
+ t.integer "score"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "world_score"
+ t.integer "french_score"
+ end
+
+ create_table "world_countries", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "code", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "world_participants", force: :cascade do |t|
+ t.string "username", null: false
+ t.string "name", null: false
+ t.integer "world_country_id", null: false
+ t.integer "qualifier_result"
+ t.integer "semifinal_result"
+ t.integer "final_result"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "round", null: false
+ t.integer "score"
+ t.index ["world_country_id"], name: "index_world_participants_on_world_country_id"
+ end
+
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "categories", "contests"
@@ -220,4 +251,5 @@ ActiveRecord::Schema[8.0].define(version: 2026_08_25_071844) do
add_foreign_key "offlines", "contests"
add_foreign_key "puzzles", "contests"
add_foreign_key "sessions", "users"
+ add_foreign_key "world_participants", "world_countries"
end
diff --git a/spec/factories/world_bets.rb b/spec/factories/world_bets.rb
new file mode 100644
index 0000000..fc1906a
--- /dev/null
+++ b/spec/factories/world_bets.rb
@@ -0,0 +1,20 @@
+# == Schema Information
+#
+# Table name: world_bets
+#
+# id :integer not null, primary key
+# bet :string
+# french_score :integer
+# name :string
+# score :integer
+# world_score :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+FactoryBot.define do
+ factory :world_bet do
+ name { "MyString" }
+ bet { "MyString" }
+ score { 1 }
+ end
+end
diff --git a/spec/factories/world_countries.rb b/spec/factories/world_countries.rb
new file mode 100644
index 0000000..1e4c1bd
--- /dev/null
+++ b/spec/factories/world_countries.rb
@@ -0,0 +1,16 @@
+# == Schema Information
+#
+# Table name: world_countries
+#
+# id :integer not null, primary key
+# code :string not null
+# name :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+FactoryBot.define do
+ factory :world_country do
+ name { "MyString" }
+ code { "MyString" }
+ end
+end
diff --git a/spec/factories/world_participants.rb b/spec/factories/world_participants.rb
new file mode 100644
index 0000000..3327b53
--- /dev/null
+++ b/spec/factories/world_participants.rb
@@ -0,0 +1,34 @@
+# == Schema Information
+#
+# Table name: world_participants
+#
+# id :integer not null, primary key
+# final_result :integer
+# name :string not null
+# qualifier_result :integer
+# round :string not null
+# score :integer
+# semifinal_result :integer
+# username :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# world_country_id :integer not null
+#
+# Indexes
+#
+# index_world_participants_on_world_country_id (world_country_id)
+#
+# Foreign Keys
+#
+# world_country_id (world_country_id => world_countries.id)
+#
+FactoryBot.define do
+ factory :world_participant do
+ username { "MyString" }
+ name { "MyString" }
+ world_country { nil }
+ qualifier_result { 1 }
+ semifinal_result { 1 }
+ final_result { 1 }
+ end
+end
diff --git a/spec/helpers/worlds_helper_spec.rb b/spec/helpers/worlds_helper_spec.rb
new file mode 100644
index 0000000..3659250
--- /dev/null
+++ b/spec/helpers/worlds_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the WorldsHelper. For example:
+#
+# describe WorldsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe WorldsHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/world_bet_spec.rb b/spec/models/world_bet_spec.rb
new file mode 100644
index 0000000..05cbb96
--- /dev/null
+++ b/spec/models/world_bet_spec.rb
@@ -0,0 +1,18 @@
+require 'rails_helper'
+
+# == Schema Information
+#
+# Table name: world_bets
+#
+# id :integer not null, primary key
+# bet :string
+# french_score :integer
+# name :string
+# score :integer
+# world_score :integer
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+RSpec.describe WorldBet, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/world_country_spec.rb b/spec/models/world_country_spec.rb
new file mode 100644
index 0000000..9079d10
--- /dev/null
+++ b/spec/models/world_country_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# == Schema Information
+#
+# Table name: world_countries
+#
+# id :integer not null, primary key
+# code :string not null
+# name :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+#
+RSpec.describe WorldCountry, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/world_participant_spec.rb b/spec/models/world_participant_spec.rb
new file mode 100644
index 0000000..df9d0e2
--- /dev/null
+++ b/spec/models/world_participant_spec.rb
@@ -0,0 +1,29 @@
+require 'rails_helper'
+
+# == Schema Information
+#
+# Table name: world_participants
+#
+# id :integer not null, primary key
+# final_result :integer
+# name :string not null
+# qualifier_result :integer
+# round :string not null
+# score :integer
+# semifinal_result :integer
+# username :string not null
+# created_at :datetime not null
+# updated_at :datetime not null
+# world_country_id :integer not null
+#
+# Indexes
+#
+# index_world_participants_on_world_country_id (world_country_id)
+#
+# Foreign Keys
+#
+# world_country_id (world_country_id => world_countries.id)
+#
+RSpec.describe WorldParticipant, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/requests/worlds_spec.rb b/spec/requests/worlds_spec.rb
new file mode 100644
index 0000000..342bc30
--- /dev/null
+++ b/spec/requests/worlds_spec.rb
@@ -0,0 +1,7 @@
+require 'rails_helper'
+
+RSpec.describe "Worlds", type: :request do
+ describe "GET /index" do
+ pending "add some examples (or delete) #{__FILE__}"
+ end
+end