From b7ca8dee43b06da259c8f1c2be617251d2ceaa0a Mon Sep 17 00:00:00 2001 From: Ben Anderson Date: Fri, 25 Apr 2025 10:11:58 +1200 Subject: [PATCH] Refactor db/seeds to allow for easy development seeds In order to try out different things and qa changes locally it can be helpful to have various data in your development database. This just makes it a little easier and safer to load that data into the dev database repeatedly. Also fix up how languages.yml are loaded by switching to YAML.safe_load and dropping the symbol keys in favour of using HashWithIndifferentAccess. --- db/languages.yml | 301 ++++++++++++++++++++-------------------- db/seeds.rb | 83 +---------- db/seeds/common.rb | 50 +++++++ db/seeds/development.rb | 26 ++++ 4 files changed, 235 insertions(+), 225 deletions(-) create mode 100644 db/seeds/common.rb create mode 100644 db/seeds/development.rb diff --git a/db/languages.yml b/db/languages.yml index 12645ff24..1b4765b8d 100644 --- a/db/languages.yml +++ b/db/languages.yml @@ -6,174 +6,177 @@ # interpreter_command is executed directly using 'isolate' (so the first argument must be the full path to the interpreter) c++: - :name: "C++" - :lexer: c++ - :compiled: yes - :interpreted: no - :current: c++17 - :extension: .cpp - :source_filename: program - :exe_extension: .exe - :processes: 1 - :variants: - c++03: # deprecated - :name: "C++03" - :compiler: g++ - :compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu++03 -O2 -o %{output} %{source} -lm" + name: "C++" + lexer: c++ + compiled: yes + interpreted: no + current: c++17 + extension: .cpp + source_filename: program + exe_extension: .exe + processes: 1 + variants: + c++03: # deprecated + name: "C++03" + compiler: g++ + compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu++03 -O2 -o %{output} %{source} -lm" c++11: - :name: "C++11" - :compiler: g++ - :compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu++11 -O2 -o %{output} %{source} -lm" - c++14: # Changes since C++11: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1319r0.html - :name: "C++14" - :compiler: g++ - :compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu++14 -O2 -o %{output} %{source} -lm" - c++17: # Changes since C++14: https://isocpp.org/files/papers/p0636r0.html - :name: "C++17" - :compiler: g++ - :compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu++17 -O2 -o %{output} %{source} -lm" + name: "C++11" + compiler: g++ + compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu++11 -O2 -o %{output} %{source} -lm" + c++14: # Changes since C++11: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1319r0.html + name: "C++14" + compiler: g++ + compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu++14 -O2 -o %{output} %{source} -lm" + c++17: # Changes since C++14: https://isocpp.org/files/papers/p0636r0.html + name: "C++17" + compiler: g++ + compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu++17 -O2 -o %{output} %{source} -lm" c: - :name: "C" - :lexer: c - :compiled: yes - :interpreted: no - :current: c11 - :extension: .c - :source_filename: program - :exe_extension: .exe - :processes: 1 - :variants: - c99: # deprecated - :name: C99 - :compiler: gcc - :compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu99 -O2 -o %{output} %{source} -lm" - c11: # Changes since c99: https://en.wikipedia.org/wiki/C11_(C_standard_revision) - :name: C11 - :compiler: gcc - :compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu11 -O2 -o %{output} %{source} -lm" + name: "C" + lexer: c + compiled: yes + interpreted: no + current: c11 + extension: .c + source_filename: program + exe_extension: .exe + processes: 1 + variants: + c99: # deprecated + name: C99 + compiler: gcc + compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu99 -O2 -o %{output} %{source} -lm" + c11: # Changes since c99: https://en.wikipedia.org/wiki/C11_(C_standard_revision) + name: C11 + compiler: gcc + compiler_command: "%{compiler} --version | head -n 1 1>&2 && %{compiler} -std=gnu11 -O2 -o %{output} %{source} -lm" java: - :name: Java - :lexer: java - :compiled: yes - :interpreted: yes - :current: java11 - :extension: .java - :source_filename: Main - :exe_extension: .jar - :processes: 0 - :variants: + name: Java + lexer: java + compiled: yes + interpreted: yes + current: java11 + extension: .java + source_filename: Main + exe_extension: .jar + processes: 0 + variants: java6: - :name: Java 1.6 # deprecated (note: uses '-source 1.6', which will be removed in a future JDK release) - :compiler: javac;jar - :compiler_command: '%{compiler[0]} -version | head -n 1 1>&2 && %{compiler[0]} -O -source 1.6 -J-Xms16m -J-Xmx256m %{source} && %{compiler[1]} cfe %{output} Main -C %{source_dir} .' - :interpreter: /usr/bin/java - :interpreter_command: '%{interpreter} -jar %{source}' + name: Java 1.6 # deprecated (note: uses '-source 1.6', which will be removed in a future JDK release) + compiler: javac;jar + compiler_command: "%{compiler[0]} -version | head -n 1 1>&2 && %{compiler[0]} -O -source 1.6 -J-Xms16m -J-Xmx256m %{source} && %{compiler[1]} cfe %{output} Main -C %{source_dir} ." + interpreter: /usr/bin/java + interpreter_command: "%{interpreter} -jar %{source}" java11: - :name: Java 11 - :compiler: javac;jar - :compiler_command: '%{compiler[0]} -version | head -n 1 1>&2 && %{compiler[0]} -O -source 11 -J-Xms16m -J-Xmx256m %{source} && %{compiler[1]} cfe %{output} Main -C %{source_dir} .' - :interpreter: /usr/bin/java - :interpreter_command: '%{interpreter} -jar %{source}' + name: Java 11 + compiler: javac;jar + compiler_command: "%{compiler[0]} -version | head -n 1 1>&2 && %{compiler[0]} -O -source 11 -J-Xms16m -J-Xmx256m %{source} && %{compiler[1]} cfe %{output} Main -C %{source_dir} ." + interpreter: /usr/bin/java + interpreter_command: "%{interpreter} -jar %{source}" + haskell: - :name: Haskell - :lexer: haskell - :compiled: yes - :interpreted: no - :current: haskell2010 - :extension: .hs - :source_filename: program - :exe_extension: .exe - :processes: 1 - :variants: + name: Haskell + lexer: haskell + compiled: yes + interpreted: no + current: haskell2010 + extension: .hs + source_filename: program + exe_extension: .exe + processes: 1 + variants: haskell2010: - :name: "Haskell 2010" - :compiler: ghc - :compiler_command: '%{compiler} --version 1>&2 && %{compiler} --make -O2 -o %{output} %{source} -lm' + name: "Haskell 2010" + compiler: ghc + compiler_command: "%{compiler} --version 1>&2 && %{compiler} --make -O2 -o %{output} %{source} -lm" + python: - :name: Python - :lexer: python - :compiled: no - :interpreted: yes - :current: python3.8 - :extension: .py - :source_filename: program - :processes: 1 - :interpreter_command: '%{interpreter} %{source}' - :compiler_command: '%{interpreter} -m py_compile %{source}' - :variants: - python2: # deprecated - Python 2 reached end-of-life in late 2019 - :name: "Python 2.7" - :interpreter: /usr/bin/python2 - python3.4: # deprecated - :name: "Python 3.4" - :interpreter: /usr/bin/python3.4 + name: Python + lexer: python + compiled: no + interpreted: yes + current: python3.8 + extension: .py + source_filename: program + processes: 1 + interpreter_command: "%{interpreter} %{source}" + compiler_command: "%{interpreter} -m py_compile %{source}" + variants: + python2: # deprecated - Python 2 reached end-of-life in late 2019 + name: "Python 2.7" + interpreter: /usr/bin/python2 + python3.4: # deprecated + name: "Python 3.4" + interpreter: /usr/bin/python3.4 python3.8: - :name: "Python 3.8" - :interpreter: /usr/bin/python3.8 + name: "Python 3.8" + interpreter: /usr/bin/python3.8 pypy3: - :name: "Python 3.6 (PyPy 7.3)" - :interpreter: /usr/bin/pypy3 + name: "Python 3.6 (PyPy 7.3)" + interpreter: /usr/bin/pypy3 ruby: - :name: Ruby - :lexer: ruby - :compiled: no - :interpreted: yes - :current: ruby2.2 - :extension: .rb - :source_filename: program - :processes: 2 # ruby seems to have an extra process - :interpreter_command: '%{interpreter} %{source}' - :compiler_command: '%{interpreter} -c %{source}' - :variants: + name: Ruby + lexer: ruby + compiled: no + interpreted: yes + current: ruby2.2 + extension: .rb + source_filename: program + processes: 2 # ruby seems to have an extra process + interpreter_command: "%{interpreter} %{source}" + compiler_command: "%{interpreter} -c %{source}" + variants: ruby2.2: - :name: "Ruby 2.2" - :interpreter: /usr/bin/ruby2.2 + name: "Ruby 2.2" + interpreter: /usr/bin/ruby2.2 + j: - :name: J - :lexer: text - :compiled: no - :interpreted: yes - :current: j8 - :extension: .ijs - :source_filename: program - :processes: 3 # needed for system/main/stdlib.ijs to shell out to uname - :interpreter_command: '%{interpreter} %{source}' - :variants: + name: J + lexer: text + compiled: no + interpreted: yes + current: j8 + extension: .ijs + source_filename: program + processes: 3 # needed for system/main/stdlib.ijs to shell out to uname + interpreter_command: "%{interpreter} %{source}" + variants: j8: - :name: "J 8.03" - :interpreter: /usr/bin/ijconsole-8.0.3 + name: "J 8.03" + interpreter: /usr/bin/ijconsole-8.0.3 javascript: - :name: JavaScript - :lexer: javascript - :compiled: no - :interpreted: yes - :current: v8_8 - :extension: .js - :source_filename: program - :interpreter_command: '%{interpreter} %{source}' - :variants: + name: JavaScript + lexer: javascript + compiled: no + interpreted: yes + current: v8_8 + extension: .js + source_filename: program + interpreter_command: "%{interpreter} %{source}" + variants: v8_8: - :name: "JavaScript (V8 8.1)" - :interpreter: /usr/local/bin/d8 - :processes: 9 + name: "JavaScript (V8 8.1)" + interpreter: /usr/local/bin/d8 + processes: 9 csharp: - :name: "C#" - :lexer: csharp - :compiled: yes - :interpreted: yes - :current: csharp8 - :extension: .cs - :source_filename: program - :exe_extension: .exe - :processes: 5 - :interpreter: /usr/local/bin/dotnet-exec # custom shell script - :variants: + name: "C#" + lexer: csharp + compiled: yes + interpreted: yes + current: csharp8 + extension: .cs + source_filename: program + exe_extension: .exe + processes: 5 + interpreter: /usr/local/bin/dotnet-exec # custom shell script + variants: csharp8: - :name: "C# 8.0" - :compiler: dotnet-csc # custom shell script (also prints SDK version number) - :compiler_command: '%{compiler} %{source} -optimize+ -out:%{output} -langversion:8.0 -nologo' - :interpreter_command: '%{interpreter} %{source}' + name: "C# 8.0" + compiler: dotnet-csc # custom shell script (also prints SDK version number) + compiler_command: "%{compiler} %{source} -optimize+ -out:%{output} -langversion:8.0 -nologo" + interpreter_command: "%{interpreter} %{source}" diff --git a/db/seeds.rb b/db/seeds.rb index 708636523..d19dc2f29 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -6,81 +6,12 @@ # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) # Mayor.create(:name => 'Daley', :city => cities.first) -if !User.exists?(0) # code in this block to be fixed - # rootuser = User.new() - # rootuser.id = 0 - # rootuser.name = "System" - # rootuser.username = "system" - # rootuser.email = "system@nztrain.com" - # rootuser.encrypted_password = "" - # rootuser.save - # no users in user table, create a user - # rootuser = User.new({:id => 0, :name => "Root User", :username => "root", :email => "root@nztrain.com", :encrypted_password => "Need an encrypted password here"}) -end +# We use different seed files for different environments, meaning we don't +# have to worry about development data leaking into prod (in the unlikely +# event we ever ran db:seed in prod), and vice-vesa. -["superadmin", "admin", "staff", "organiser", "author"].each do |role| - Role.find_or_create_by(name: role) -end +# db/seeds/common.rb is things we want in every environment +require_relative Rails.root.join("db/seeds/common") -# give superadmin status to set everything up -rootuser = nil - -if User.exists?(0) - rootuser = User.find(0) # if a root user exists and is zeroth (this is for security because normally users get positive integers as id) user in users table, give superadmin status -end - -if rootuser && rootuser.username == "root" - superadmin = Role.find_by_name("superadmin") - superadmin.users.push(rootuser) unless superadmin.users.include?(rootuser) -end - -["system/mailer/email", "system/mailer/password", "recaptcha/public_key", "recaptcha/private_key"].each do |setting| - Setting.find_or_create_by(key: setting) -end - -setting = Setting.find_or_create_by(key: "submissions/shuffle") -setting.update_attributes(value: Random.new.rand(2E9.to_i).to_s) if setting.value.nil? - -# create a special group called Everyone -if !Group.exists?(0) - everyone = Group.new - everyone.id = 0 - everyone.name = "Everyone" - everyone.owner_id = 0 - everyone.save -end - -if !User.exists?(0) - everyone = Group.find(0) - user = User.new - user.id = 0 - user.username = "System" - user.name = "System" - user.email = "nztrain@gmail.com" - user.password = "somepassword" # ensure validation succeeds - user.encrypted_password = "$2a$10$KpSeaHGXoXEwdrERTFVCPo428a2s2r4ZazR999X9abc22368nneen" # set password hash so no known password matches the hash - user.groups.push(everyone) - user.save -end - -languages = YAML.load(File.read(File.expand_path("db/languages.yml", Rails.root))) -langfields = %i[name compiler compiler_command interpreter interpreter_command lexer interpreted compiled extension source_filename exe_extension processes] -languages.each do |grpid, grpcfg| - groupopts = grpcfg.slice(:name) - group = LanguageGroup.where(identifier: grpid).first_or_create! - group.update_attributes(groupopts) - - grpcfg[:variants].each do |langid, langcfg| - langopts = langcfg.reverse_merge(grpcfg).slice(*langfields).merge(group_id: group.id) - language = Language.where(identifier: langid).first_or_create! - language.update_attributes(langopts) - end - - group.update_attributes(current_language_id: group.languages.find_by_identifier(grpcfg[:current]).id) -end - -ProblemSeries.where(identifier: "COCI").first_or_create!.update_attributes(name: "Croatian Open Competition in Informatics", importer_type: "Problems::COCI::Importer") - -Entity.find_or_create_by(name: "New Zealand Olympiad in Informatics", entity_type: "Organisation") do |entity| - entity.entity_id = Organisation.create.id -end +env_seed_path = Rails.root.join("db/seeds/#{Rails.env}.rb") +require_relative env_seed_path if File.exist?(env_seed_path) diff --git a/db/seeds/common.rb b/db/seeds/common.rb new file mode 100644 index 000000000..6c7fe2d51 --- /dev/null +++ b/db/seeds/common.rb @@ -0,0 +1,50 @@ +%w[superadmin admin staff organiser author].each do |name| + Role.find_or_create_by!(name: name) +end + +rootuser = User + .create_with( + username: "System", + name: "System", + email: "nztrain@gmail.com", + password: SecureRandom.base64(18), + roles: [Role.find_by!(name: "superadmin")] + ) + .find_or_create_by!(id: 0) + +["system/mailer/email", "system/mailer/password", "recaptcha/public_key", "recaptcha/private_key"].each do |setting| + Setting.find_or_create_by!(key: setting) +end + +Setting + .create_with(value: Random.new.rand(2E9.to_i).to_s) + .find_or_create_by!(key: "submissions/shuffle") + +Group + .create_with( + name: "Everyone", + owner: rootuser + ) + .find_or_create_by(id: 0) + +languages = YAML.safe_load(File.read(File.expand_path("db/languages.yml", Rails.root))).with_indifferent_access +langfields = %i[name compiler compiler_command interpreter interpreter_command lexer interpreted compiled extension source_filename exe_extension processes] +languages.each do |grpid, grpcfg| + groupopts = grpcfg.slice(:name) + group = LanguageGroup.where(identifier: grpid).first_or_create! + group.update_attributes(groupopts) + + grpcfg[:variants].each do |langid, langcfg| + langopts = langcfg.reverse_merge(grpcfg).slice(*langfields).merge(group_id: group.id) + language = Language.where(identifier: langid).first_or_create! + language.update_attributes(langopts) + end + + group.update_attributes(current_language_id: group.languages.find_by_identifier(grpcfg[:current]).id) +end + +ProblemSeries.where(identifier: "COCI").first_or_create!.update_attributes(name: "Croatian Open Competition in Informatics", importer_type: "Problems::COCI::Importer") + +Entity.find_or_create_by(name: "New Zealand Olympiad in Informatics", entity_type: "Organisation") do |entity| + entity.entity_id = Organisation.create.id +end diff --git a/db/seeds/development.rb b/db/seeds/development.rb new file mode 100644 index 000000000..2427db305 --- /dev/null +++ b/db/seeds/development.rb @@ -0,0 +1,26 @@ +# db/seeds/common.rb always runs before this, so we can assume +# roles etc defined there exist already. + +# Add a nice, memorable, boring set of users for local development +%w[user admin superadmin].each do |username| + User + .create_with( + name: username.capitalize, + email: "#{username}@example.com", + password: "password", + confirmed_at: Time.current + ).find_or_create_by!( + username: username + ) +end + +User.find_by!(username: "admin").update!(roles: [Role.find_by!(name: "admin")]) +User.find_by!(username: "superadmin").update!(roles: [Role.find_by!(name: "superadmin")]) + +# A problem & set +problem_set = ProblemSet.find_or_create_by!(name: "Example problems") +Problem + .create_with(problem_sets: [problem_set]) + .find_or_create_by!(name: "Example problem") + +problem_set.group_associations.find_or_create_by!(group: Group.find_by(name: "Everyone"))