mirror of
https://github.com/discourse/discourse.git
synced 2026-08-07 13:19:19 +08:00
Previously, flag names with no ASCII word characters (e.g. Chinese) all normalized to the same `name_key` of `custom_`, because `set_name_key` stripped non-`\w` characters and Ruby's `\w` is ASCII-only. The flag system keys lookups by `name_key` (the `disabled_flag_types` enum, the frontend `actionByName` map), so once two flags shared a key, disabling one of them flipped `can_act` for the whole group and hid every other flag that shared it. This makes `name_key` reliably unique: - `set_name_key` falls back to `custom_flag` when the slug is empty and appends a counter (`_2`, `_3`, ...) on collision, so distinct names always produce distinct keys. It now only re-derives the key when the name actually changes, keeping it stable across other saves. - A post-deploy migration backfills existing duplicate keys and adds the unique index on `flags.name_key` that the original `create_flags` migration intended (`unique: true` is a no-op in `create_table`). https://meta.discourse.org/t/405254
43 lines
1.3 KiB
Ruby
Vendored
43 lines
1.3 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
require Rails.root.join("db/post_migrate/20260624140945_ensure_unique_flag_name_keys.rb")
|
|
|
|
RSpec.describe EnsureUniqueFlagNameKeys do
|
|
subject(:migrate) { described_class.new.up }
|
|
|
|
before do
|
|
@verbose = ActiveRecord::Migration.verbose
|
|
ActiveRecord::Migration.verbose = false
|
|
end
|
|
|
|
after { ActiveRecord::Migration.verbose = @verbose }
|
|
|
|
it "disambiguates duplicate name_keys and enforces uniqueness" do
|
|
flag1 = Fabricate(:flag, name: "alpha")
|
|
flag2 = Fabricate(:flag, name: "beta")
|
|
flag3 = Fabricate(:flag, name: "gamma")
|
|
|
|
ActiveRecord::Base.connection.remove_index(:flags, :name_key, if_exists: true)
|
|
Flag.unscoped.where(id: [flag1.id, flag2.id, flag3.id]).update_all(name_key: "custom_")
|
|
|
|
migrate
|
|
|
|
keys = Flag.unscoped.where(id: [flag1.id, flag2.id, flag3.id]).order(:id).pluck(:name_key)
|
|
|
|
expect(keys.first).to eq("custom_")
|
|
expect(keys[1]).to eq("custom__#{flag2.id}")
|
|
expect(keys[2]).to eq("custom__#{flag3.id}")
|
|
expect(keys.uniq.size).to eq(3)
|
|
|
|
expect(ActiveRecord::Base.connection.index_exists?(:flags, :name_key, unique: true)).to eq(true)
|
|
end
|
|
|
|
it "leaves already-unique name_keys untouched" do
|
|
flag = Fabricate(:flag, name: "unique flag")
|
|
original = flag.name_key
|
|
|
|
migrate
|
|
|
|
expect(flag.reload.name_key).to eq(original)
|
|
end
|
|
end
|