DEV: Upgrade Rails to 6.1.3.1 (#12688)

Rails 6.1.3.1 deprecates a few API and has some internal changes that break our tests suite, so this commit fixes all the deprecations and errors and now Discourse should be fully compatible with Rails 6.1.3.1. We also have a new release of the rails_failover gem that's compatible with Rails 6.1.3.1.
This commit is contained in:
Osama Sayegh 2021-04-21 12:36:32 +03:00 committed by GitHub
parent 838fa12f14
commit 45ccadeeeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 116 additions and 95 deletions

14
Gemfile
View File

@ -18,13 +18,13 @@ else
# this allows us to include the bits of rails we use without pieces we do not. # this allows us to include the bits of rails we use without pieces we do not.
# #
# To issue a rails update bump the version number here # To issue a rails update bump the version number here
gem 'actionmailer', '6.0.3.5' gem 'actionmailer', '6.1.3.1'
gem 'actionpack', '6.0.3.5' gem 'actionpack', '6.1.3.1'
gem 'actionview', '6.0.3.5' gem 'actionview', '6.1.3.1'
gem 'activemodel', '6.0.3.5' gem 'activemodel', '6.1.3.1'
gem 'activerecord', '6.0.3.5' gem 'activerecord', '6.1.3.1'
gem 'activesupport', '6.0.3.5' gem 'activesupport', '6.1.3.1'
gem 'railties', '6.0.3.5' gem 'railties', '6.1.3.1'
gem 'sprockets-rails' gem 'sprockets-rails'
end end

View File

@ -8,21 +8,22 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actionmailer (6.0.3.5) actionmailer (6.1.3.1)
actionpack (= 6.0.3.5) actionpack (= 6.1.3.1)
actionview (= 6.0.3.5) actionview (= 6.1.3.1)
activejob (= 6.0.3.5) activejob (= 6.1.3.1)
activesupport (= 6.1.3.1)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.0.3.5) actionpack (6.1.3.1)
actionview (= 6.0.3.5) actionview (= 6.1.3.1)
activesupport (= 6.0.3.5) activesupport (= 6.1.3.1)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.0.9)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actionview (6.0.3.5) actionview (6.1.3.1)
activesupport (= 6.0.3.5) activesupport (= 6.1.3.1)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
@ -31,20 +32,20 @@ GEM
actionview (>= 6.0.a) actionview (>= 6.0.a)
active_model_serializers (0.8.4) active_model_serializers (0.8.4)
activemodel (>= 3.0) activemodel (>= 3.0)
activejob (6.0.3.5) activejob (6.1.3.1)
activesupport (= 6.0.3.5) activesupport (= 6.1.3.1)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.0.3.5) activemodel (6.1.3.1)
activesupport (= 6.0.3.5) activesupport (= 6.1.3.1)
activerecord (6.0.3.5) activerecord (6.1.3.1)
activemodel (= 6.0.3.5) activemodel (= 6.1.3.1)
activesupport (= 6.0.3.5) activesupport (= 6.1.3.1)
activesupport (6.0.3.5) activesupport (6.1.3.1)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 1.6, < 2)
minitest (~> 5.1) minitest (>= 5.1)
tzinfo (~> 1.1) tzinfo (~> 2.0)
zeitwerk (~> 2.2, >= 2.2.2) zeitwerk (~> 2.3)
addressable (2.7.0) addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0) public_suffix (>= 2.0.2, < 5.0)
annotate (3.1.1) annotate (3.1.1)
@ -315,19 +316,19 @@ GEM
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0) rails-html-sanitizer (1.3.0)
loofah (~> 2.3) loofah (~> 2.3)
rails_failover (0.6.5) rails_failover (0.7.3)
activerecord (~> 6.0) activerecord (~> 6.0)
concurrent-ruby concurrent-ruby
railties (~> 6.0) railties (~> 6.0)
rails_multisite (2.5.0) rails_multisite (3.0.0)
activerecord (> 5.0, < 7) activerecord (> 5.0, < 7)
railties (> 5.0, < 7) railties (> 5.0, < 7)
railties (6.0.3.5) railties (6.1.3.1)
actionpack (= 6.0.3.5) actionpack (= 6.1.3.1)
activesupport (= 6.0.3.5) activesupport (= 6.1.3.1)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (~> 1.0)
rainbow (3.0.0) rainbow (3.0.0)
raindrops (0.19.1) raindrops (0.19.1)
rake (13.0.3) rake (13.0.3)
@ -444,10 +445,9 @@ GEM
stackprof (0.2.16) stackprof (0.2.16)
test-prof (1.0.2) test-prof (1.0.2)
thor (1.1.0) thor (1.1.0)
thread_safe (0.3.6)
tilt (2.0.10) tilt (2.0.10)
tzinfo (1.2.9) tzinfo (2.0.4)
thread_safe (~> 0.1) concurrent-ruby (~> 1.0)
uglifier (4.2.0) uglifier (4.2.0)
execjs (>= 0.3.0, < 3) execjs (>= 0.3.0, < 3)
unf (0.1.4) unf (0.1.4)
@ -479,14 +479,14 @@ PLATFORMS
x86_64-linux x86_64-linux
DEPENDENCIES DEPENDENCIES
actionmailer (= 6.0.3.5) actionmailer (= 6.1.3.1)
actionpack (= 6.0.3.5) actionpack (= 6.1.3.1)
actionview (= 6.0.3.5) actionview (= 6.1.3.1)
actionview_precompiler actionview_precompiler
active_model_serializers (~> 0.8.3) active_model_serializers (~> 0.8.3)
activemodel (= 6.0.3.5) activemodel (= 6.1.3.1)
activerecord (= 6.0.3.5) activerecord (= 6.1.3.1)
activesupport (= 6.0.3.5) activesupport (= 6.1.3.1)
addressable addressable
annotate annotate
aws-sdk-s3 aws-sdk-s3
@ -566,7 +566,7 @@ DEPENDENCIES
rack-protection rack-protection
rails_failover rails_failover
rails_multisite rails_multisite
railties (= 6.0.3.5) railties (= 6.1.3.1)
rake rake
rb-fsevent rb-fsevent
rbtrace rbtrace

View File

@ -72,7 +72,7 @@ class Admin::BackupsController < Admin::AdminController
def show def show
if !EmailBackupToken.compare(current_user.id, params.fetch(:token)) if !EmailBackupToken.compare(current_user.id, params.fetch(:token))
@error = I18n.t('download_backup_mailer.no_token') @error = I18n.t('download_backup_mailer.no_token')
return render template: 'admin/backups/show.html.erb', layout: 'no_ember', status: 422 return render layout: 'no_ember', status: 422, formats: [:html]
end end
store = BackupRestore::BackupStore.create store = BackupRestore::BackupStore.create

View File

@ -11,7 +11,7 @@ class MetadataController < ApplicationController
def opensearch def opensearch
expires_in 1.minutes expires_in 1.minutes
render template: "metadata/opensearch.xml" render template: "metadata/opensearch", formats: [:xml]
end end
def app_association_android def app_association_android

View File

@ -29,7 +29,7 @@ class StaticController < ApplicationController
if map.has_key?(@page) if map.has_key?(@page)
site_setting_key = map[@page][:redirect] site_setting_key = map[@page][:redirect]
url = SiteSetting.get(site_setting_key) url = SiteSetting.get(site_setting_key)
return redirect_to(url) unless url.blank? return redirect_to(url) if url.present?
end end
# The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting. # The /guidelines route ALWAYS shows our FAQ, ignoring the faq_url site setting.
@ -70,12 +70,8 @@ class StaticController < ApplicationController
cookies[:email] = { value: params[:email], expires: 1.day.from_now } cookies[:email] = { value: params[:email], expires: 1.day.from_now }
end end
file = "static/#{@page}.#{I18n.locale}" if lookup_context.find_all("static/#{@page}").any?
file = "static/#{@page}.en" if lookup_context.find_all("#{file}.html").empty? render "static/#{@page}", layout: !request.xhr?, formats: [:html]
file = "static/#{@page}" if lookup_context.find_all("#{file}.html").empty?
if lookup_context.find_all("#{file}.html").any?
render file, layout: !request.xhr?, formats: [:html]
return return
end end

View File

@ -369,6 +369,8 @@ module ApplicationHelper
def loading_admin? def loading_admin?
return false unless defined?(controller) return false unless defined?(controller)
return false if controller.class.name.blank?
controller.class.name.split("::").first == "Admin" controller.class.name.split("::").first == "Admin"
end end

View File

@ -77,7 +77,7 @@ class EmailToken < ActiveRecord::Base
if user if user
if Invite.redeem_from_email(user.email).present? if Invite.redeem_from_email(user.email).present?
return user.reload user.reload
end end
user user
end end

View File

@ -6,11 +6,9 @@ InviteRedeemer = Struct.new(:invite, :email, :username, :name, :password, :user_
Invite.transaction do Invite.transaction do
if invite_was_redeemed? if invite_was_redeemed?
process_invitation process_invitation
return invited_user invited_user
end end
end end
nil
end end
# extracted from User cause it is very specific to invites # extracted from User cause it is very specific to invites

View File

@ -26,18 +26,18 @@ class PublishedPage < ActiveRecord::Base
def self.publish!(publisher, topic, slug, options = {}) def self.publish!(publisher, topic, slug, options = {})
pp = nil pp = nil
transaction do results = transaction do
pp = find_or_initialize_by(topic: topic) pp = find_or_initialize_by(topic: topic)
pp.slug = slug.strip pp.slug = slug.strip
pp.public = options[:public] || false pp.public = options[:public] || false
if pp.save if pp.save
StaffActionLogger.new(publisher).log_published_page(topic.id, slug) StaffActionLogger.new(publisher).log_published_page(topic.id, slug)
return [true, pp] [true, pp]
end end
end end
[false, pp] results || [false, pp]
end end
def self.unpublish!(publisher, topic) def self.unpublish!(publisher, topic)

View File

@ -811,10 +811,12 @@ class StaffActionLogger
changes.delete("updated_at") changes.delete("updated_at")
old_values = [] old_values = []
new_values = [] new_values = []
changes.each do |k, v| changes
old_values << "#{k}: #{v[0]}" .sort_by { |k, _| k.to_s }
new_values << "#{k}: #{v[1]}" .each do |k, v|
end old_values << "#{k}: #{v[0]}"
new_values << "#{k}: #{v[1]}"
end
[old_values, new_values] [old_values, new_values]
end end

View File

@ -9,11 +9,10 @@ class UserNotificationRenderer < ActionView::Base
def self.render(*args) def self.render(*args)
LOCK.synchronize do LOCK.synchronize do
@instance ||= UserNotificationRenderer.with_view_paths( @instance ||= UserNotificationRenderer.with_empty_template_cache.with_view_paths(
Rails.configuration.paths["app/views"] Rails.configuration.paths["app/views"]
) )
@instance.render(*args) @instance.render(*args)
end end
end end
end end

View File

@ -110,7 +110,7 @@ module BackupRestore
DatabaseConfiguration = Struct.new(:host, :port, :username, :password, :database) DatabaseConfiguration = Struct.new(:host, :port, :username, :password, :database)
def self.database_configuration def self.database_configuration
config = ActiveRecord::Base.connection_pool.spec.config config = ActiveRecord::Base.connection_pool.db_config.configuration_hash
config = config.with_indifferent_access config = config.with_indifferent_access
# credentials for PostgreSQL in CI environment # credentials for PostgreSQL in CI environment

View File

@ -5,17 +5,16 @@ class BookmarkReminderNotificationHandler
return if bookmark.blank? return if bookmark.blank?
Bookmark.transaction do Bookmark.transaction do
if bookmark.post.blank? || bookmark.post.deleted_at.present? if bookmark.post.blank? || bookmark.post.deleted_at.present?
return clear_reminder(bookmark) clear_reminder(bookmark)
elsif bookmark.topic
create_notification(bookmark)
if bookmark.auto_delete_when_reminder_sent?
BookmarkManager.new(bookmark.user).destroy(bookmark.id)
end
clear_reminder(bookmark)
end end
return unless bookmark.topic
create_notification(bookmark)
if bookmark.auto_delete_when_reminder_sent?
BookmarkManager.new(bookmark.user).destroy(bookmark.id)
end
clear_reminder(bookmark)
end end
end end

View File

@ -5,7 +5,7 @@ require_dependency "migration/base_dropper"
class DbHelper class DbHelper
REMAP_SQL ||= <<~SQL REMAP_SQL ||= <<~SQL
SELECT table_name, column_name, character_maximum_length SELECT table_name::text, column_name::text, character_maximum_length
FROM information_schema.columns FROM information_schema.columns
WHERE table_schema = 'public' WHERE table_schema = 'public'
AND is_updatable = 'YES' AND is_updatable = 'YES'
@ -14,7 +14,7 @@ class DbHelper
SQL SQL
TRIGGERS_SQL ||= <<~SQL TRIGGERS_SQL ||= <<~SQL
SELECT trigger_name SELECT trigger_name::text
FROM information_schema.triggers FROM information_schema.triggers
WHERE trigger_name LIKE '%_readonly' WHERE trigger_name LIKE '%_readonly'
SQL SQL

View File

@ -54,9 +54,7 @@ class ActiveRecord::Relation
enforce_raw_sql_whitelist(column_names) enforce_raw_sql_whitelist(column_names)
relation = spawn relation = spawn
relation.select_values = column_names.map { |cn| relation.select_values = column_names
@klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn
}
klass.connection.select_raw(relation.arel) do |result, _| klass.connection.select_raw(relation.arel) do |result, _|
result.type_map = DB.type_map result.type_map = DB.type_map

View File

@ -14,6 +14,8 @@ module I18n
def reload! def reload!
@pluralizers = {} @pluralizers = {}
# this calls `reload!` in our patch lib/freedom_patches/translate_accelerator.rb
I18n.reload!
super super
end end

View File

@ -61,7 +61,7 @@ module Migration
def self.existing_discourse_function_names def self.existing_discourse_function_names
DB.query_single(<<~SQL) DB.query_single(<<~SQL)
SELECT routine_name SELECT routine_name::text
FROM information_schema.routines FROM information_schema.routines
WHERE routine_type = 'FUNCTION' AND specific_schema = '#{FUNCTION_SCHEMA_NAME}' WHERE routine_type = 'FUNCTION' AND specific_schema = '#{FUNCTION_SCHEMA_NAME}'
SQL SQL

View File

@ -82,7 +82,9 @@ module TurboTests
def check_for_migrations def check_for_migrations
config = config =
ActiveRecord::Base ActiveRecord::Base
.configurations["test"] .configurations
.find_db_config("test")
.configuration_hash
.merge("database" => "discourse_test_1") .merge("database" => "discourse_test_1")
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = ['db/migrate', 'db/post_migrate'] ActiveRecord::Tasks::DatabaseTasks.migrations_paths = ['db/migrate', 'db/post_migrate']

View File

@ -2,21 +2,36 @@
class UniqueAmongValidator < ActiveRecord::Validations::UniquenessValidator class UniqueAmongValidator < ActiveRecord::Validations::UniquenessValidator
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
old_errors = record.errors[attribute].size old_errors = []
record.errors.each do |error|
old_errors << error if error.attribute == attribute
end
# look for any duplicates at all # look for any duplicates at all
super super
new_errors = record.errors[attribute].size - old_errors new_errors = []
record.errors.each do |error|
new_errors << error if error.attribute == attribute
end
# do nothing further unless there were some duplicates. # do nothing further unless there were some duplicates.
unless new_errors == 0 if new_errors.size - old_errors.size != 0
# now look only in the collection we care about. # now look only in the collection we care about.
dupes = options[:collection].call(record).where("lower(#{attribute}) = ?", value.downcase) dupes = options[:collection].call(record).where("lower(#{attribute}) = ?", value.downcase)
dupes = dupes.where("id != ?", record.id) if record.persisted? dupes = dupes.where("id != ?", record.id) if record.persisted?
# pop off the error, if it was a false positive # pop off the error, if it was a false positive
record.errors[attribute].pop(new_errors) unless dupes.exists? if !dupes.exists?
record.errors.delete(attribute)
old_errors.each do |error|
record.errors.add(
error.attribute,
error.type,
**error.options
)
end
end
end end
end end

View File

@ -324,6 +324,15 @@ describe Topic do
it "won't allow another topic to be created with the same name in same category" do it "won't allow another topic to be created with the same name in same category" do
expect(new_topic).not_to be_valid expect(new_topic).not_to be_valid
end end
it "other errors will not be cleared" do
SiteSetting.min_topic_title_length = 5
topic.update!(title: "more than 5 characters but less than 134")
SiteSetting.min_topic_title_length = 134
new_topic_different_cat.title = "more than 5 characters but less than 134"
expect(new_topic_different_cat).not_to be_valid
expect(new_topic_different_cat.errors[:title]).to include(I18n.t("errors.messages.too_short", count: 134))
end
end end
end end

View File

@ -11,7 +11,7 @@ describe UserNotificationSchedule do
user: user, user: user,
enabled: true enabled: true
}) })
expect(schedule.errors.keys).to eq([ expect(schedule.errors.attribute_names).to eq([
:day_0_start_time, :day_0_start_time,
:day_0_end_time, :day_0_end_time,
:day_1_start_time, :day_1_start_time,

View File

@ -116,6 +116,7 @@ RSpec.describe Admin::BackupsController do
expect(response.status).to eq(422) expect(response.status).to eq(422)
expect(response.headers['Content-Disposition']).not_to match(/attachment; filename/) expect(response.headers['Content-Disposition']).not_to match(/attachment; filename/)
expect(response.body).to include(I18n.t("download_backup_mailer.no_token"))
end end
end end

View File

@ -40,7 +40,7 @@ describe Admin::EmbeddableHostsController do
history_exists = UserHistory.where( history_exists = UserHistory.where(
acting_user_id: admin.id, acting_user_id: admin.id,
action: UserHistory.actions[:embeddable_host_update], action: UserHistory.actions[:embeddable_host_update],
new_value: "host: test.com, class_name: test-class, category_id: #{category.id}").exists? new_value: "category_id: #{category.id}, class_name: test-class, host: test.com").exists?
expect(history_exists).to eq(true) expect(history_exists).to eq(true)

View File

@ -69,7 +69,7 @@ describe Admin::WebHooksController do
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(UserHistory.where(acting_user_id: admin.id, expect(UserHistory.where(acting_user_id: admin.id,
action: UserHistory.actions[:web_hook_update], action: UserHistory.actions[:web_hook_update],
new_value: "payload_url: https://test.com, active: false").exists?).to eq(true) new_value: "active: false, payload_url: https://test.com").exists?).to eq(true)
end end
end end

View File

@ -282,9 +282,7 @@ RSpec.describe ApplicationController do
get "/search/query.json", params: { trem: "misspelled term" } get "/search/query.json", params: { trem: "misspelled term" }
expect(response.status).to eq(400) expect(response.status).to eq(400)
expect(response.parsed_body).to eq( expect(response.parsed_body["errors"].first).to include("param is missing or the value is empty: term")
"errors" => ["param is missing or the value is empty: term"]
)
end end
end end