From 362dd83b07cbc227e930a31498d6269267a2703a Mon Sep 17 00:00:00 2001 From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:21:58 +0100 Subject: [PATCH 1/7] Upgrade to rails 8 --- Gemfile | 2 +- Gemfile.lock | 118 +++++++++++++++++++++++++-------------------------- 2 files changed, 58 insertions(+), 62 deletions(-) diff --git a/Gemfile b/Gemfile index 322112698..de3232fac 100644 --- a/Gemfile +++ b/Gemfile @@ -41,7 +41,7 @@ gem 'propshaft' gem 'puma', '~> 7.2' gem 'rack_content_type_default', '~> 1.1' gem 'rack-cors' -gem 'rails', '~> 7.1' +gem 'rails', '8.0.5' gem 'ruby-progressbar', '~> 1.13', require: false gem 'sentry-rails' gem 'statesman' diff --git a/Gemfile.lock b/Gemfile.lock index 0cc789d5b..5c89ae0cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,68 +11,65 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.2.3.1) - actionpack (= 7.2.3.1) - activesupport (= 7.2.3.1) + actioncable (8.0.5) + actionpack (= 8.0.5) + activesupport (= 8.0.5) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.2.3.1) - actionpack (= 7.2.3.1) - activejob (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + actionmailbox (8.0.5) + actionpack (= 8.0.5) + activejob (= 8.0.5) + activerecord (= 8.0.5) + activestorage (= 8.0.5) + activesupport (= 8.0.5) mail (>= 2.8.0) - actionmailer (7.2.3.1) - actionpack (= 7.2.3.1) - actionview (= 7.2.3.1) - activejob (= 7.2.3.1) - activesupport (= 7.2.3.1) + actionmailer (8.0.5) + actionpack (= 8.0.5) + actionview (= 8.0.5) + activejob (= 8.0.5) + activesupport (= 8.0.5) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (7.2.3.1) - actionview (= 7.2.3.1) - activesupport (= 7.2.3.1) - cgi + actionpack (8.0.5) + actionview (= 8.0.5) + activesupport (= 8.0.5) nokogiri (>= 1.8.5) - racc - rack (>= 2.2.4, < 3.3) + rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (7.2.3.1) - actionpack (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + actiontext (8.0.5) + actionpack (= 8.0.5) + activerecord (= 8.0.5) + activestorage (= 8.0.5) + activesupport (= 8.0.5) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.2.3.1) - activesupport (= 7.2.3.1) + actionview (8.0.5) + activesupport (= 8.0.5) builder (~> 3.1) - cgi erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (7.2.3.1) - activesupport (= 7.2.3.1) + activejob (8.0.5) + activesupport (= 8.0.5) globalid (>= 0.3.6) - activemodel (7.2.3.1) - activesupport (= 7.2.3.1) - activerecord (7.2.3.1) - activemodel (= 7.2.3.1) - activesupport (= 7.2.3.1) + activemodel (8.0.5) + activesupport (= 8.0.5) + activerecord (8.0.5) + activemodel (= 8.0.5) + activesupport (= 8.0.5) timeout (>= 0.4.0) - activestorage (7.2.3.1) - actionpack (= 7.2.3.1) - activejob (= 7.2.3.1) - activerecord (= 7.2.3.1) - activesupport (= 7.2.3.1) + activestorage (8.0.5) + actionpack (= 8.0.5) + activejob (= 8.0.5) + activerecord (= 8.0.5) + activesupport (= 8.0.5) marcel (~> 1.0) - activesupport (7.2.3.1) + activesupport (8.0.5) base64 benchmark (>= 0.3) bigdecimal @@ -81,9 +78,10 @@ GEM drb i18n (>= 1.6, < 2) logger (>= 1.4.2) - minitest (>= 5.1, < 6) + minitest (>= 5.1) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) administrate (1.0.0) @@ -136,7 +134,6 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - cgi (0.5.1) childprocess (4.1.0) choice (0.2.0) climate_control (1.2.0) @@ -379,20 +376,20 @@ GEM rack rackup (2.3.1) rack (>= 3) - rails (7.2.3.1) - actioncable (= 7.2.3.1) - actionmailbox (= 7.2.3.1) - actionmailer (= 7.2.3.1) - actionpack (= 7.2.3.1) - actiontext (= 7.2.3.1) - actionview (= 7.2.3.1) - activejob (= 7.2.3.1) - activemodel (= 7.2.3.1) - activerecord (= 7.2.3.1) - activestorage (= 7.2.3.1) - activesupport (= 7.2.3.1) + rails (8.0.5) + actioncable (= 8.0.5) + actionmailbox (= 8.0.5) + actionmailer (= 8.0.5) + actionpack (= 8.0.5) + actiontext (= 8.0.5) + actionview (= 8.0.5) + activejob (= 8.0.5) + activemodel (= 8.0.5) + activerecord (= 8.0.5) + activestorage (= 8.0.5) + activesupport (= 8.0.5) bundler (>= 1.15.0) - railties (= 7.2.3.1) + railties (= 8.0.5) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -405,10 +402,9 @@ GEM rails-html-sanitizer (1.7.0) loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.2.3.1) - actionpack (= 7.2.3.1) - activesupport (= 7.2.3.1) - cgi + railties (8.0.5) + actionpack (= 8.0.5) + activesupport (= 8.0.5) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -617,7 +613,7 @@ DEPENDENCIES puma (~> 7.2) rack-cors rack_content_type_default (~> 1.1) - rails (~> 7.1) + rails (= 8.0.5) rails-erd rspec rspec-rails From 05aba7c689bc6ff1edd7d74ebc6f110a0a8d1199 Mon Sep 17 00:00:00 2001 From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:27:27 +0100 Subject: [PATCH 2/7] Add binstubs created by rails update --- bin/dev | 2 ++ bin/rubocop | 8 ++++++++ bin/setup | 23 +++++++++++------------ 3 files changed, 21 insertions(+), 12 deletions(-) create mode 100755 bin/dev create mode 100755 bin/rubocop diff --git a/bin/dev b/bin/dev new file mode 100755 index 000000000..5f91c2054 --- /dev/null +++ b/bin/dev @@ -0,0 +1,2 @@ +#!/usr/bin/env ruby +exec "./bin/rails", "server", *ARGV diff --git a/bin/rubocop b/bin/rubocop new file mode 100755 index 000000000..8a20ba1a4 --- /dev/null +++ b/bin/rubocop @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +require "rubygems" +require "bundler/setup" + +# explicit rubocop config increases performance slightly while avoiding config confusion. +ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) + +load Gem.bin_path("rubocop", "rubocop") diff --git a/bin/setup b/bin/setup index 3a74034f1..be3db3c0d 100755 --- a/bin/setup +++ b/bin/setup @@ -1,10 +1,7 @@ #!/usr/bin/env ruby -# frozen_string_literal: true +require "fileutils" -require 'fileutils' - -# path to your application root. -APP_ROOT = File.expand_path('..', __dir__) +APP_ROOT = File.expand_path("..", __dir__) def system!(*args) system(*args, exception: true) @@ -15,9 +12,8 @@ FileUtils.chdir APP_ROOT do # This script is idempotent, so that you can run it at any time and get an expectable outcome. # Add necessary setup steps to this file. - puts '== Installing dependencies ==' - system! 'gem install bundler --conservative' - system('bundle check') || system!('bundle install') + puts "== Installing dependencies ==" + system("bundle check") || system!("bundle install") # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") @@ -25,11 +21,14 @@ FileUtils.chdir APP_ROOT do # end puts "\n== Preparing database ==" - system! 'bin/rails db:prepare' + system! "bin/rails db:prepare" puts "\n== Removing old logs and tempfiles ==" - system! 'bin/rails log:clear tmp:clear' + system! "bin/rails log:clear tmp:clear" - puts "\n== Restarting application server ==" - system! 'bin/rails restart' + unless ARGV.include?("--skip-server") + puts "\n== Starting development server ==" + STDOUT.flush # flush the output before exec(2) so that it displays + exec "bin/dev" + end end From b9c9c3bdc7c4996e502ba25129551806368a5b3d Mon Sep 17 00:00:00 2001 From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:42:01 +0100 Subject: [PATCH 3/7] Apply the new regex timeout default For now I've just applied the regex timeout as it's a useful security improvement and is unlikely to cause issues. --- .../new_framework_defaults_8_0.rb | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 config/initializers/new_framework_defaults_8_0.rb diff --git a/config/initializers/new_framework_defaults_8_0.rb b/config/initializers/new_framework_defaults_8_0.rb new file mode 100644 index 000000000..a6456ed34 --- /dev/null +++ b/config/initializers/new_framework_defaults_8_0.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Be sure to restart your server when you modify this file. +# +# This file eases your Rails 8.0 framework defaults upgrade. +# +# Uncomment each configuration one by one to switch to the new default. +# Once your application is ready to run with all new defaults, you can remove +# this file and set the `config.load_defaults` to `8.0`. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. +# https://guides.rubyonrails.org/upgrading_ruby_on_rails.html + +### +# Specifies whether `to_time` methods preserve the UTC offset of their receivers or preserves the timezone. +# If set to `:zone`, `to_time` methods will use the timezone of their receivers. +# If set to `:offset`, `to_time` methods will use the UTC offset. +# If `false`, `to_time` methods will convert to the local system UTC offset instead. +#++ +# Rails.application.config.active_support.to_time_preserves_timezone = :zone + +### +# When both `If-Modified-Since` and `If-None-Match` are provided by the client +# only consider `If-None-Match` as specified by RFC 7232 Section 6. +# If set to `false` both conditions need to be satisfied. +#++ +# Rails.application.config.action_dispatch.strict_freshness = true + +### +# Set `Regexp.timeout` to `1`s by default to improve security over Regexp Denial-of-Service attacks. +#++ +Regexp.timeout = 1 From 1dcc5358ca5b4509cb913c3dc8e57b44b48e7aa2 Mon Sep 17 00:00:00 2001 From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:55:42 +0100 Subject: [PATCH 4/7] Fix rubocop errors for use of expect Rails 8 introduces 'expect' that raises 400 errors when params are unexpected types rather than 500s. --- .../api/class_members_controller.rb | 2 +- app/controllers/api/google_auth_controller.rb | 2 +- .../api/projects/remixes_controller.rb | 24 ++++++----- .../api/school_classes_controller.rb | 4 +- .../api/school_students_controller.rb | 2 +- .../api/school_teachers_controller.rb | 2 +- app/controllers/api/schools_controller.rb | 40 +++++++++---------- 7 files changed, 39 insertions(+), 37 deletions(-) diff --git a/app/controllers/api/class_members_controller.rb b/app/controllers/api/class_members_controller.rb index 8bff19838..a00d104de 100644 --- a/app/controllers/api/class_members_controller.rb +++ b/app/controllers/api/class_members_controller.rb @@ -87,7 +87,7 @@ def members_existing_in_profile end def class_member_params - params.require(:class_member).permit(:user_id, :type) + params.expect(class_member: %i[user_id type]) end def create_batch_params diff --git a/app/controllers/api/google_auth_controller.rb b/app/controllers/api/google_auth_controller.rb index b18873d2d..d6b36b59f 100644 --- a/app/controllers/api/google_auth_controller.rb +++ b/app/controllers/api/google_auth_controller.rb @@ -48,7 +48,7 @@ def response_error_message def google_token_params params.require(:google_auth).require(:code) params.require(:google_auth).require(:redirect_uri) - params.require(:google_auth).permit(:code, :redirect_uri) + params.expect(google_auth: %i[code redirect_uri]) end end end diff --git a/app/controllers/api/projects/remixes_controller.rb b/app/controllers/api/projects/remixes_controller.rb index e61a19667..4ccbd5009 100644 --- a/app/controllers/api/projects/remixes_controller.rb +++ b/app/controllers/api/projects/remixes_controller.rb @@ -53,17 +53,19 @@ def load_and_authorize_remix end def remix_params - params.require(:project) - .permit(:name, - :identifier, - :project_type, - :locale, - :user_id, - :videos, - :audio, - :instructions, - image_list: [], - components: %i[id name extension content index]) + params + .expect(project: [:name, + :identifier, + :project_type, + :locale, + :user_id, + :videos, + :audio, + :instructions, + { + image_list: [], + components: [%i[id name extension content index]] + }]) end end end diff --git a/app/controllers/api/school_classes_controller.rb b/app/controllers/api/school_classes_controller.rb index 7774044be..b8366f4b6 100644 --- a/app/controllers/api/school_classes_controller.rb +++ b/app/controllers/api/school_classes_controller.rb @@ -192,11 +192,11 @@ def load_and_authorize_school_class def school_class_params # A school teacher may only create classes they own. - params.require(:school_class).permit(:name, :description) + params.expect(school_class: %i[name description]) end def import_school_class_params - params.require(:school_class).permit(:name, :description, :import_origin, :import_id) + params.expect(school_class: %i[name description import_origin import_id]) end def import_school_students_params diff --git a/app/controllers/api/school_students_controller.rb b/app/controllers/api/school_students_controller.rb index c403fc031..299f5eda3 100644 --- a/app/controllers/api/school_students_controller.rb +++ b/app/controllers/api/school_students_controller.rb @@ -167,7 +167,7 @@ def remove_students(student_ids) end def school_student_params - params.require(:school_student).permit(:username, :password, :name) + params.expect(school_student: %i[username password name]) end def school_students_params diff --git a/app/controllers/api/school_teachers_controller.rb b/app/controllers/api/school_teachers_controller.rb index 3407bfee4..3dde9f4ec 100644 --- a/app/controllers/api/school_teachers_controller.rb +++ b/app/controllers/api/school_teachers_controller.rb @@ -30,7 +30,7 @@ def create private def school_teacher_params - params.require(:school_teacher).permit(:email_address) + params.expect(school_teacher: [:email_address]) end end end diff --git a/app/controllers/api/schools_controller.rb b/app/controllers/api/schools_controller.rb index 075973360..52427efe9 100644 --- a/app/controllers/api/schools_controller.rb +++ b/app/controllers/api/schools_controller.rb @@ -77,26 +77,26 @@ def import private def school_params - params.require(:school).permit( - :name, - :website, - :reference, - :district_name, - :district_nces_id, - :school_roll_number, - :address_line_1, - :address_line_2, - :municipality, - :administrative_area, - :postal_code, - :country_code, - :creator_role, - :creator_department, - :creator_agree_authority, - :creator_agree_terms_and_conditions, - :creator_agree_to_ux_contact, - :creator_agree_responsible_safeguarding, - :user_origin + params.expect( + school: %i[name + website + reference + district_name + district_nces_id + school_roll_number + address_line_1 + address_line_2 + municipality + administrative_area + postal_code + country_code + creator_role + creator_department + creator_agree_authority + creator_agree_terms_and_conditions + creator_agree_to_ux_contact + creator_agree_responsible_safeguarding + user_origin] ) end end From 76e56e1b761e050ea2d494b6a64d1b3a3771a6ad Mon Sep 17 00:00:00 2001 From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com> Date: Wed, 15 Apr 2026 15:01:01 +0100 Subject: [PATCH 5/7] Fix failing test due to error message changing in Rails --- spec/requests/api_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/api_controller_spec.rb b/spec/requests/api_controller_spec.rb index d4cff479a..f8f2b37cd 100644 --- a/spec/requests/api_controller_spec.rb +++ b/spec/requests/api_controller_spec.rb @@ -46,7 +46,7 @@ def index get '/test' expect(response.parsed_body).to include( - 'error' => 'ActionController::ParameterMissing: param is missing or the value is empty: foo' + 'error' => 'ActionController::ParameterMissing: param is missing or the value is empty or invalid: foo' ) end end From 7b3d7ae4fcec59f057d4b63b505e9c3c514de33e Mon Sep 17 00:00:00 2001 From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:55:33 +0100 Subject: [PATCH 6/7] Upgrade to Rails 8.1 --- Gemfile | 2 +- Gemfile.lock | 114 ++++++++++++++++++++++++++------------------------- 2 files changed, 59 insertions(+), 57 deletions(-) diff --git a/Gemfile b/Gemfile index de3232fac..ba24cd2ec 100644 --- a/Gemfile +++ b/Gemfile @@ -41,7 +41,7 @@ gem 'propshaft' gem 'puma', '~> 7.2' gem 'rack_content_type_default', '~> 1.1' gem 'rack-cors' -gem 'rails', '8.0.5' +gem 'rails', '~> 8.1' gem 'ruby-progressbar', '~> 1.13', require: false gem 'sentry-rails' gem 'statesman' diff --git a/Gemfile.lock b/Gemfile.lock index 5c89ae0cc..284df0d41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,29 +11,31 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (8.0.5) - actionpack (= 8.0.5) - activesupport (= 8.0.5) + action_text-trix (2.1.18) + railties + actioncable (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.0.5) - actionpack (= 8.0.5) - activejob (= 8.0.5) - activerecord (= 8.0.5) - activestorage (= 8.0.5) - activesupport (= 8.0.5) + actionmailbox (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) - actionmailer (8.0.5) - actionpack (= 8.0.5) - actionview (= 8.0.5) - activejob (= 8.0.5) - activesupport (= 8.0.5) + actionmailer (8.1.3) + actionpack (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.0.5) - actionview (= 8.0.5) - activesupport (= 8.0.5) + actionpack (8.1.3) + actionview (= 8.1.3) + activesupport (= 8.1.3) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -41,42 +43,43 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.0.5) - actionpack (= 8.0.5) - activerecord (= 8.0.5) - activestorage (= 8.0.5) - activesupport (= 8.0.5) + actiontext (8.1.3) + action_text-trix (~> 2.1.15) + actionpack (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.0.5) - activesupport (= 8.0.5) + actionview (8.1.3) + activesupport (= 8.1.3) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.0.5) - activesupport (= 8.0.5) + activejob (8.1.3) + activesupport (= 8.1.3) globalid (>= 0.3.6) - activemodel (8.0.5) - activesupport (= 8.0.5) - activerecord (8.0.5) - activemodel (= 8.0.5) - activesupport (= 8.0.5) + activemodel (8.1.3) + activesupport (= 8.1.3) + activerecord (8.1.3) + activemodel (= 8.1.3) + activesupport (= 8.1.3) timeout (>= 0.4.0) - activestorage (8.0.5) - actionpack (= 8.0.5) - activejob (= 8.0.5) - activerecord (= 8.0.5) - activesupport (= 8.0.5) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) marcel (~> 1.0) - activesupport (8.0.5) + activesupport (8.1.3) base64 - benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + json logger (>= 1.4.2) minitest (>= 5.1) securerandom (>= 0.3) @@ -114,7 +117,6 @@ GEM aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) base64 (0.3.0) - benchmark (0.5.0) bigdecimal (4.1.1) bootsnap (1.23.0) msgpack (~> 1.2) @@ -376,20 +378,20 @@ GEM rack rackup (2.3.1) rack (>= 3) - rails (8.0.5) - actioncable (= 8.0.5) - actionmailbox (= 8.0.5) - actionmailer (= 8.0.5) - actionpack (= 8.0.5) - actiontext (= 8.0.5) - actionview (= 8.0.5) - activejob (= 8.0.5) - activemodel (= 8.0.5) - activerecord (= 8.0.5) - activestorage (= 8.0.5) - activesupport (= 8.0.5) + rails (8.1.3) + actioncable (= 8.1.3) + actionmailbox (= 8.1.3) + actionmailer (= 8.1.3) + actionpack (= 8.1.3) + actiontext (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activemodel (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) bundler (>= 1.15.0) - railties (= 8.0.5) + railties (= 8.1.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -402,9 +404,9 @@ GEM rails-html-sanitizer (1.7.0) loofah (~> 2.25) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (8.0.5) - actionpack (= 8.0.5) - activesupport (= 8.0.5) + railties (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -613,7 +615,7 @@ DEPENDENCIES puma (~> 7.2) rack-cors rack_content_type_default (~> 1.1) - rails (= 8.0.5) + rails (~> 8.1) rails-erd rspec rspec-rails From ff7b90b6687c97bda149707239d5ae3480198986 Mon Sep 17 00:00:00 2001 From: Chris Zetter <253059100+zetter-rpf@users.noreply.github.com> Date: Wed, 15 Apr 2026 14:56:17 +0100 Subject: [PATCH 7/7] Apply updated Schema Rails 8.1 sorts tables alphabetically --- db/schema.rb | 242 +++++++++++++++++++++++++-------------------------- 1 file changed, 121 insertions(+), 121 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 1699e359f..51f338700 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,30 +10,30 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2026_03_23_152328) do +ActiveRecord::Schema[8.1].define(version: 2026_03_23_152328) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_catalog.plpgsql" enable_extension "pgcrypto" - enable_extension "plpgsql" create_table "active_storage_attachments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "name", null: false - t.string "record_type", null: false - t.uuid "record_id", null: false t.uuid "blob_id", null: false t.datetime "created_at", null: false + t.string "name", null: false + t.uuid "record_id", null: false + t.string "record_type", null: false t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true end create_table "active_storage_blobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "key", null: false - t.string "filename", null: false - t.string "content_type" - t.text "metadata" - t.string "service_name", null: false t.bigint "byte_size", null: false t.string "checksum" + t.string "content_type" t.datetime "created_at", null: false + t.string "filename", null: false + t.string "key", null: false + t.text "metadata" + t.string "service_name", null: false t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true end @@ -44,9 +44,9 @@ end create_table "class_students", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false t.uuid "school_class_id", null: false t.uuid "student_id", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["school_class_id", "student_id"], name: "index_class_students_on_school_class_id_and_student_id", unique: true t.index ["school_class_id"], name: "index_class_students_on_school_class_id" @@ -54,9 +54,9 @@ end create_table "class_teachers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false t.uuid "school_class_id", null: false t.uuid "teacher_id", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["school_class_id", "teacher_id"], name: "index_class_teachers_on_school_class_id_and_teacher_id", unique: true t.index ["school_class_id"], name: "index_class_teachers_on_school_class_id" @@ -64,73 +64,73 @@ end create_table "components", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "project_id" - t.string "name", null: false - t.string "extension", null: false t.string "content" t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.boolean "default", default: false, null: false + t.string "extension", null: false + t.string "name", null: false + t.uuid "project_id" + t.datetime "updated_at", null: false t.index ["project_id"], name: "index_components_on_project_id" end create_table "feedback", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "school_project_id" t.text "content" - t.uuid "user_id", null: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.datetime "read_at" + t.uuid "school_project_id" + t.datetime "updated_at", null: false + t.uuid "user_id", null: false t.index ["school_project_id"], name: "index_feedback_on_school_project_id" end create_table "flipper_features", force: :cascade do |t| - t.string "key", null: false t.datetime "created_at", null: false + t.string "key", null: false t.datetime "updated_at", null: false t.index ["key"], name: "index_flipper_features_on_key", unique: true end create_table "flipper_gates", force: :cascade do |t| + t.datetime "created_at", null: false t.string "feature_key", null: false t.string "key", null: false - t.text "value" - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.text "value" t.index ["feature_key", "key", "value"], name: "index_flipper_gates_on_feature_key_and_key_and_value", unique: true end create_table "good_job_batches", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.integer "callback_priority" + t.text "callback_queue_name" t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.text "description" - t.jsonb "serialized_properties" - t.text "on_finish" - t.text "on_success" - t.text "on_discard" - t.text "callback_queue_name" - t.integer "callback_priority" - t.datetime "enqueued_at" t.datetime "discarded_at" + t.datetime "enqueued_at" t.datetime "finished_at" t.datetime "jobs_finished_at" + t.text "on_discard" + t.text "on_finish" + t.text "on_success" + t.jsonb "serialized_properties" + t.datetime "updated_at", null: false end create_table "good_job_executions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.uuid "active_job_id", null: false - t.text "job_class" - t.text "queue_name" - t.jsonb "serialized_params" - t.datetime "scheduled_at" - t.datetime "finished_at" + t.string "concurrency_key" + t.datetime "created_at", null: false + t.interval "duration" t.text "error" - t.integer "error_event", limit: 2 t.text "error_backtrace", array: true + t.integer "error_event", limit: 2 + t.datetime "finished_at" + t.text "job_class" t.uuid "process_id" - t.interval "duration" - t.string "concurrency_key" + t.text "queue_name" + t.datetime "scheduled_at" + t.jsonb "serialized_params" + t.datetime "updated_at", null: false t.index ["active_job_id", "created_at"], name: "index_good_job_executions_on_active_job_id_and_created_at" t.index ["concurrency_key"], name: "index_good_job_executions_on_concurrency_key" t.index ["process_id", "created_at"], name: "index_good_job_executions_on_process_id_and_created_at" @@ -138,43 +138,43 @@ create_table "good_job_processes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.jsonb "state" t.integer "lock_type", limit: 2 + t.jsonb "state" + t.datetime "updated_at", null: false end create_table "good_job_settings", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.text "key" + t.datetime "updated_at", null: false t.jsonb "value" t.index ["key"], name: "index_good_job_settings_on_key", unique: true end create_table "good_jobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.text "queue_name" - t.integer "priority" - t.jsonb "serialized_params" - t.datetime "scheduled_at" - t.datetime "performed_at" - t.datetime "finished_at" - t.text "error" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.uuid "active_job_id" + t.uuid "batch_callback_id" + t.uuid "batch_id" t.text "concurrency_key" - t.text "cron_key" - t.uuid "retried_good_job_id" + t.datetime "created_at", null: false t.datetime "cron_at" - t.uuid "batch_id" - t.uuid "batch_callback_id" - t.boolean "is_discrete" + t.text "cron_key" + t.text "error" + t.integer "error_event", limit: 2 t.integer "executions_count" + t.datetime "finished_at" + t.boolean "is_discrete" t.text "job_class" - t.integer "error_event", limit: 2 t.text "labels", array: true - t.uuid "locked_by_id" t.datetime "locked_at" + t.uuid "locked_by_id" + t.datetime "performed_at" + t.integer "priority" + t.text "queue_name" + t.uuid "retried_good_job_id" + t.datetime "scheduled_at" + t.jsonb "serialized_params" + t.datetime "updated_at", null: false t.index ["active_job_id", "created_at"], name: "index_good_jobs_on_active_job_id_and_created_at" t.index ["batch_callback_id"], name: "index_good_jobs_on_batch_callback_id", where: "(batch_callback_id IS NOT NULL)" t.index ["batch_id"], name: "index_good_jobs_on_batch_id", where: "(batch_id IS NOT NULL)" @@ -194,17 +194,17 @@ end create_table "lessons", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "school_id" - t.uuid "school_class_id" + t.datetime "archived_at" t.uuid "copied_from_id" - t.uuid "user_id", null: false - t.string "name", null: false + t.datetime "created_at", null: false t.string "description" - t.string "visibility", default: "teachers", null: false t.datetime "due_date" - t.datetime "archived_at" - t.datetime "created_at", null: false + t.string "name", null: false + t.uuid "school_class_id" + t.uuid "school_id" t.datetime "updated_at", null: false + t.uuid "user_id", null: false + t.string "visibility", default: "teachers", null: false t.index ["archived_at"], name: "index_lessons_on_archived_at" t.index ["copied_from_id"], name: "index_lessons_on_copied_from_id" t.index ["name"], name: "index_lessons_on_name" @@ -215,28 +215,28 @@ end create_table "project_errors", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "project_id" + t.datetime "created_at", null: false t.string "error", null: false t.string "error_type" - t.uuid "user_id" - t.datetime "created_at", null: false + t.uuid "project_id" t.datetime "updated_at", null: false + t.uuid "user_id" t.index ["project_id"], name: "index_project_errors_on_project_id" end create_table "projects", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "user_id" - t.string "name" - t.string "identifier", null: false - t.string "project_type", default: "python", null: false t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.uuid "remixed_from_id" + t.string "identifier", null: false + t.text "instructions" + t.uuid "lesson_id" t.string "locale" + t.string "name" + t.string "project_type", default: "python", null: false t.string "remix_origin" + t.uuid "remixed_from_id" t.uuid "school_id" - t.uuid "lesson_id" - t.text "instructions" + t.datetime "updated_at", null: false + t.uuid "user_id" t.index ["identifier", "locale"], name: "index_projects_on_identifier_and_locale", unique: true t.index ["identifier"], name: "index_projects_on_identifier" t.index ["lesson_id"], name: "index_projects_on_lesson_id" @@ -245,88 +245,88 @@ end create_table "roles", force: :cascade do |t| - t.uuid "user_id" - t.uuid "school_id" - t.integer "role" t.datetime "created_at", null: false + t.integer "role" + t.uuid "school_id" t.datetime "updated_at", null: false + t.uuid "user_id" t.index ["school_id"], name: "index_roles_on_school_id" t.index ["user_id", "school_id", "role"], name: "index_roles_on_user_id_and_school_id_and_role", unique: true t.index ["user_id"], name: "index_roles_on_user_id" end create_table "school_classes", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "school_id", null: false - t.string "name", null: false + t.string "code" t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.string "description" - t.string "code" - t.integer "import_origin" t.string "import_id" + t.integer "import_origin" + t.string "name", null: false + t.uuid "school_id", null: false + t.datetime "updated_at", null: false t.index ["code", "school_id"], name: "index_school_classes_on_code_and_school_id", unique: true t.index ["school_id"], name: "index_school_classes_on_school_id" end create_table "school_import_results", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false t.uuid "job_id", null: false - t.uuid "user_id", null: false t.jsonb "results", default: {}, null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.uuid "user_id", null: false t.index ["job_id"], name: "index_school_import_results_on_job_id", unique: true t.index ["user_id"], name: "index_school_import_results_on_user_id" end create_table "school_project_transitions", force: :cascade do |t| + t.datetime "created_at", null: false t.string "from_state", null: false - t.string "to_state", null: false t.text "metadata", default: "{}" - t.integer "sort_key", null: false - t.uuid "school_project_id", null: false t.boolean "most_recent", null: false - t.datetime "created_at", null: false + t.uuid "school_project_id", null: false + t.integer "sort_key", null: false + t.string "to_state", null: false t.datetime "updated_at", null: false t.index ["school_project_id", "most_recent"], name: "index_school_project_transitions_parent_most_recent", unique: true, where: "most_recent" t.index ["school_project_id", "sort_key"], name: "index_school_project_transitions_parent_sort", unique: true end create_table "school_projects", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "school_id" - t.uuid "project_id", null: false - t.boolean "finished", default: false t.datetime "created_at", null: false + t.boolean "finished", default: false + t.uuid "project_id", null: false + t.uuid "school_id" t.datetime "updated_at", null: false t.index ["project_id"], name: "index_school_projects_on_project_id" t.index ["school_id"], name: "index_school_projects_on_school_id" end create_table "schools", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "name", null: false - t.string "reference" t.string "address_line_1", null: false t.string "address_line_2" - t.string "municipality", null: false t.string "administrative_area" - t.string "postal_code" + t.string "code" t.string "country_code", null: false - t.datetime "verified_at" t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.datetime "rejected_at" - t.uuid "creator_id" - t.string "website", null: false - t.string "creator_role" - t.string "creator_department" t.boolean "creator_agree_authority" + t.boolean "creator_agree_responsible_safeguarding", default: true t.boolean "creator_agree_terms_and_conditions" - t.string "code" t.boolean "creator_agree_to_ux_contact", default: false - t.boolean "creator_agree_responsible_safeguarding", default: true - t.integer "user_origin", default: 0 + t.string "creator_department" + t.uuid "creator_id" + t.string "creator_role" t.string "district_name" t.string "district_nces_id" + t.string "municipality", null: false + t.string "name", null: false + t.string "postal_code" + t.string "reference" + t.datetime "rejected_at" t.string "school_roll_number" + t.datetime "updated_at", null: false + t.integer "user_origin", default: 0 + t.datetime "verified_at" + t.string "website", null: false t.index ["code"], name: "index_schools_on_code", unique: true t.index ["creator_id"], name: "index_schools_on_creator_id_active_only", unique: true, where: "(rejected_at IS NULL)" t.index ["reference"], name: "index_schools_on_reference", unique: true, where: "(rejected_at IS NULL)" @@ -334,49 +334,49 @@ end create_table "scratch_assets", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "filename", null: false t.datetime "created_at", null: false + t.string "filename", null: false t.datetime "updated_at", null: false t.index ["filename"], name: "index_scratch_assets_on_filename", unique: true end create_table "scratch_components", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| t.jsonb "content" - t.uuid "project_id", null: false t.datetime "created_at", null: false + t.uuid "project_id", null: false t.datetime "updated_at", null: false t.index ["project_id"], name: "index_scratch_components_on_project_id", unique: true end create_table "teacher_invitations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "accepted_at" + t.datetime "created_at", null: false t.string "email_address" t.uuid "school_id", null: false - t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.datetime "accepted_at" t.index ["school_id"], name: "index_teacher_invitations_on_school_id" end create_table "user_jobs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.uuid "user_id", null: false - t.uuid "good_job_id" t.datetime "created_at", null: false - t.datetime "updated_at", null: false t.uuid "good_job_batch_id" + t.uuid "good_job_id" + t.datetime "updated_at", null: false + t.uuid "user_id", null: false t.index ["user_id", "good_job_id"], name: "index_user_jobs_on_user_id_and_good_job_id", unique: true end create_table "versions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| - t.string "item_type", null: false - t.string "item_id", null: false - t.string "event", null: false - t.string "whodunnit" t.datetime "created_at" - t.json "object_changes" + t.string "event", null: false + t.string "item_id", null: false + t.string "item_type", null: false t.uuid "meta_project_id" - t.uuid "meta_school_id" t.uuid "meta_remixed_from_id" + t.uuid "meta_school_id" t.string "meta_school_project_id" + t.json "object_changes" + t.string "whodunnit" t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id" end