Db:seed_fu 在升级过程中于 002_groups.rb 失败:验证失败:名称已被占用

在从 v2026.3.0-latest 升级时遇到了问题。rake db:seed_fu 任务在处理 002_groups.rb 时失败,报错如下:

ActiveRecord::RecordInvalid: Validation failed: Name has already been taken. (ActiveRecord::RecordInvalid)

崩溃发生在种子数据尝试初始化新系统组(ID 4: anonymousID 5: logged_in_users)时。

已通过 Rails 控制台确认。手动检查在验证阶段失败,但绕过验证后,数据库可以毫无问题地插入该记录:

# 这会报错“Name has already been taken”
g = Group.new(id: 4, name: "anonymous", automatic: true)
g.valid?

# 这能正常工作,证明实际上不存在冲突
g.save(validate: false)

我通过手动创建这些新系统组解决了这个问题:

ActiveRecord::Base.transaction do
  g4 = Group.new(id: 4, name: "anonymous", automatic: true)
  g4.save(validate: false)

  g5 = Group.new(id: 5, name: "logged_in_users", automatic: true)
  g5.save(validate: false)
end

现在,当我运行 rake db:seed_fu 时,它能顺利完成且无报错。

完整堆栈跟踪:

# rake --trace db:seed_fu
** Invoke db:seed_fu (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute db:seed_fu

== Seed from /var/www/discourse/db/fixtures/001_refresh.rb

== Seed from /var/www/discourse/db/fixtures/002_groups.rb
rake aborted!
ActiveRecord::RecordInvalid: Validation failed: Name has already been taken (ActiveRecord::RecordInvalid)
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/validations.rb:87:in 'ActiveRecord::Validations#raise_validation_error'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/validations.rb:54:in 'ActiveRecord::Validations#save!'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:365:in 'block in ActiveRecord::Transactions#save!'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:417:in 'block (2 levels) in ActiveRecord::Transactions#with_transaction_returning_status'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:357:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:413:in 'block in ActiveRecord::Transactions#with_transaction_returning_status'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in 'ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_handling.rb:312:in 'ActiveRecord::ConnectionHandling#with_connection'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:409:in 'ActiveRecord::Transactions#with_transaction_returning_status'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:365:in 'ActiveRecord::Transactions#save!'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/suppressor.rb:56:in 'ActiveRecord::Suppressor#save!'
/var/www/discourse/app/models/group.rb:539:in 'Group.refresh_automatic_group!'
/var/www/discourse/app/models/group.rb:719:in 'block in Group.ensure_automatic_groups!'
/var/www/discourse/app/models/group.rb:719:in 'Hash#each_key'
/var/www/discourse/app/models/group.rb:719:in 'Group.ensure_automatic_groups!'
(eval at /var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:46):3:in 'block (2 levels) in SeedFu::Runner#run_file'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:46:in 'Kernel#eval'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:46:in 'block (2 levels) in SeedFu::Runner#run_file'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:58:in 'block in SeedFu::Runner#open'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:57:in 'IO.open'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:57:in 'SeedFu::Runner#open'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:36:in 'block in SeedFu::Runner#run_file'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/transaction.rb:626:in 'block in ActiveRecord::ConnectionAdapters::TransactionManager#within_new_transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activesupport-8.0.5/lib/active_support/concurrency/null_lock.rb:9:in 'ActiveSupport::Concurrency::NullLock#synchronize'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/transaction.rb:623:in 'ActiveRecord::ConnectionAdapters::TransactionManager#within_new_transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:367:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#within_new_transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/database_statements.rb:359:in 'ActiveRecord::ConnectionAdapters::DatabaseStatements#transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:233:in 'block in ActiveRecord::Transactions::ClassMethods#transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in 'ActiveRecord::ConnectionAdapters::ConnectionPool#with_connection'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/connection_handling.rb:312:in 'ActiveRecord::ConnectionHandling#with_connection'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/activerecord-8.0.5/lib/active_record/transactions.rb:232:in 'ActiveRecord::Transactions::ClassMethods#transaction'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:35:in 'SeedFu::Runner#run_file'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:26:in 'block in SeedFu::Runner#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:25:in 'Array#each'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/seed-fu/runner.rb:25:in 'SeedFu::Runner#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/discourse-seed-fu.rb:29:in 'SeedFu.seed'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/discourse-seed-fu-2.3.12/lib/tasks/seed_fu.rake:36:in 'block (2 levels) in <main>'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:281:in 'block in Rake::Task#execute'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:281:in 'Array#each'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:281:in 'Rake::Task#execute'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:219:in 'block in Rake::Task#invoke_with_call_chain'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:199:in 'Monitor#synchronize'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:199:in 'Rake::Task#invoke_with_call_chain'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/task.rb:188:in 'Rake::Task#invoke'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:183:in 'Rake::Application#invoke_task'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:139:in 'block (2 levels) in Rake::Application#top_level'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:139:in 'Array#each'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:139:in 'block in Rake::Application#top_level'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:148:in 'Rake::Application#run_with_threads'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:133:in 'Rake::Application#top_level'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:84:in 'block in Rake::Application#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:209:in 'Rake::Application#standard_exception_handling'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/rake-13.4.2/lib/rake/application.rb:81:in 'Rake::Application#run'
bin/rake:13:in '<top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:59:in 'Kernel.load'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:59:in 'Bundler::CLI::Exec#kernel_load'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli/exec.rb:23:in 'Bundler::CLI::Exec#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:452:in 'Bundler::CLI#exec'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/command.rb:28:in 'Bundler::Thor::Command#run'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in 'Bundler::Thor::Invocation#invoke_command'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor.rb:538:in 'Bundler::Thor.dispatch'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:35:in 'Bundler::CLI.dispatch'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/vendor/thor/lib/thor/base.rb:584:in 'Bundler::Thor::Base::ClassMethods#start'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/cli.rb:29:in 'Bundler::CLI.start'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/exe/bundle:28:in 'block in <top (required)>'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/lib/bundler/friendly_errors.rb:117:in 'Bundler.with_friendly_errors'
/var/www/discourse/vendor/bundle/ruby/3.4.0/gems/bundler-2.6.4/exe/bundle:20:in '<top (required)>'
/usr/local/bin/bundle:25:in 'Kernel#load'
/usr/local/bin/bundle:25:in '<main>'
Tasks: TOP => db:seed_fu
2 个赞

“我也一样!”按钮去哪了?

当已存在名为 anonymous群组或用户(!!!)时,就会出现此问题。

我们有许多论坛在导入后将 anonymous 用作用户名。

该提交中提到:

本 PR 引入了两个新的自动群组:anonymous_users 和 logged_in_users

但似乎该群组最终被命名为 anonymous,而没有加上 _users

这很遗憾,因为:

  • anonymous 让人无法明确它是用户群组还是单个用户
  • 缺少 _users 后缀会大大增加与现有群组或用户发生冲突的风险

建议的解决方案:

  1. 将该群组命名为 anonymous_users,这样与 logged_in_users 更一致,并能极大降低冲突风险
  2. 至少检测到冲突时,重命名现有的用户或群组,而不是直接报错退出
2 个赞

正在查看此问题。

有一个迁移脚本来处理此类名称冲突,因此不清楚为何在原帖(OP)的情况下未生效:

您是在运行迁移之前单独执行此命令吗?

您的迁移脚本检查了现有组。然而,它也应检查现有用户,因为用户和组共享同一命名空间,因此不能存在与用户同名的组。

嗯,好吧,这确实挺烦人的……我现在要提交一个 PR 来修改这个问题:

正如你所说,这应该能大幅降低冲突的可能性。我非常有把握,目前不会有用户名叫 anonymous_users,所以如果我要执行这个重命名,我认为不需要进行自动检查或迁移。

1 个赞

好的,这应该能解决问题,同时也修复了 Granular group-based permissions for anonymous and logged in users 中即将实施的变更逻辑存在的一些问题:

https://github.com/discourse/discourse/pull/40435

2 个赞

最初并没有。它在标准重建过程中运行迁移时失败了,这也是我最初注意到问题并追踪到原因的方式。之后我手动运行了 rake 任务进行故障排查。

2 个赞

好的,我已经合并了该 PR,应该能解决名称冲突的问题。

1 个赞

这是个好消息!

您是否也可以将修复版本回移植到 2026.5

已在此处完成 DEV: Rename anonymous group to anonymous_users (#40435) by martin-brennan · Pull Request #40629 · discourse/discourse · GitHub

1 个赞