FIX: Prevent field type migration from poisoning AR cache (#27549)

We previously migrated field_type from a string to an integer backed enum. Part of this involved renaming a column in a post migration, swapping out field_type:string for field_type:integer. This borks the ActiveRecord cache since the application is already running. Rebooting fixes it, but we want to avoid having this happen in the first place.
This commit is contained in:
Ted Johansson 2024-06-20 16:24:48 +08:00 committed by GitHub
parent dd329d55a5
commit 920aa2dfce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 36 additions and 10 deletions

View File

@ -6,6 +6,7 @@ class UserField < ActiveRecord::Base
include HasSanitizableFields include HasSanitizableFields
deprecate_column :required, drop_from: "3.3" deprecate_column :required, drop_from: "3.3"
self.ignored_columns += %i[field_type]
validates_presence_of :description validates_presence_of :description
validates_presence_of :name, unless: -> { field_type == "confirm" } validates_presence_of :name, unless: -> { field_type == "confirm" }
@ -19,7 +20,8 @@ class UserField < ActiveRecord::Base
scope :public_fields, -> { where(show_on_profile: true).or(where(show_on_user_card: true)) } scope :public_fields, -> { where(show_on_profile: true).or(where(show_on_user_card: true)) }
enum :requirement, { optional: 0, for_all_users: 1, on_signup: 2 }.freeze enum :requirement, { optional: 0, for_all_users: 1, on_signup: 2 }.freeze
enum :field_type, { text: 0, confirm: 1, dropdown: 2, multiselect: 3 }.freeze enum :field_type_enum, { text: 0, confirm: 1, dropdown: 2, multiselect: 3 }.freeze
alias_attribute :field_type, :field_type_enum
def self.max_length def self.max_length
2048 2048
@ -60,5 +62,5 @@ end
# external_type :string # external_type :string
# searchable :boolean default(FALSE), not null # searchable :boolean default(FALSE), not null
# requirement :integer default("optional"), not null # requirement :integer default("optional"), not null
# field_type :integer not null # field_type_enum :integer not null
# #

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class AddBackFieldTypeEnumToUserFields < ActiveRecord::Migration[7.0]
def change
# NOTE: This is here to undo the swap done in SwapFieldTypeWithFieldTypeEnumOnUserFields,
# as that change was breaking the AR cache until the application is rebooted.
# The condition here is to ensure it's only executed if that post-migration has been
# applied.
if !ActiveRecord::Base.connection.column_exists?(:user_fields, :field_type_enum)
add_column :user_fields, :field_type_enum, :integer
change_column_null :user_fields, :field_type, true
execute(<<~SQL)
UPDATE user_fields
SET field_type_enum = field_type
SQL
change_column_null :user_fields, :field_type_enum, false
end
end
end

View File

@ -1,14 +1,17 @@
# frozen_string_literal: true # frozen_string_literal: true
class SwapFieldTypeWithFieldTypeEnumOnUserFields < ActiveRecord::Migration[7.0] class SwapFieldTypeWithFieldTypeEnumOnUserFields < ActiveRecord::Migration[7.0]
DROPPED_COLUMNS ||= { user_fields: %i[field_type] } # DROPPED_COLUMNS ||= { user_fields: %i[field_type] }
def up # def up
DROPPED_COLUMNS.each { |table, columns| Migration::ColumnDropper.execute_drop(table, columns) } # # WARNING: Swapping in a column of a different type in a post-migration will break the AR
rename_column :user_fields, :field_type_enum, :field_type # # cache, since the application is already booted, requiring a restart.
end # #
# DROPPED_COLUMNS.each { |table, columns| Migration::ColumnDropper.execute_drop(table, columns) }
# rename_column :user_fields, :field_type_enum, :field_type
# end
def down # def down
raise ActiveRecord::IrreversibleMigration # raise ActiveRecord::IrreversibleMigration
end # end
end end