Db:seed_fu échoue sur 002_groups.rb lors de la mise à niveau : Validation échouée : Nom déjà pris

J’ai rencontré un problème lors de la mise à niveau depuis v2026.3.0-latest. La tâche rake db:seed_fu échoue lorsqu’elle atteint 002_groups.rb avec l’erreur suivante :

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

Le plantage se produit exactement lorsque le seed tente d’initialiser les nouveaux groupes système (ID 4 : anonymous et ID 5 : logged_in_users).

J’ai confirmé cela via la console Rails. Une vérification manuelle échoue lors de la validation, mais contourner la validation permet d’insérer l’enregistrement dans la base de données sans aucun problème :

# Cela échoue avec « Name has already been taken »
g = Group.new(id: 4, name: "anonymous", automatic: true)
g.valid?

# Cela fonctionne, prouvant qu'il n'y a pas de conflit réel
g.save(validate: false)

J’ai réussi à surmonter cet obstacle en créant manuellement ces nouveaux groupes système :

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

Désormais, lorsque j’exécute rake db:seed_fu, la tâche se termine sans erreur.

Pile d’appels complète :

# 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

Où est passée le bouton « Moi aussi ! » ?

Cela se produit lorsque vous avez un groupe ou un utilisateur (!!!) existant nommé anonymous.

Nous avons de nombreux forums où anonymous a été utilisé comme nom d’utilisateur après une importation.
Le commit mentionne :

Cette PR introduit deux nouveaux groupes automatiques : anonymous_users et logged_in_users

mais apparemment, le groupe a fini par être nommé anonymous sans _users.

C’est regrettable car :

  • anonymous rend flou s’il s’agit d’un groupe d’utilisateurs ou d’un seul utilisateur
  • le risque de conflit avec un groupe ou un utilisateur existant est beaucoup plus élevé sans le _users

Solutions suggérées :
1 - nommer le groupe anonymous_users après tout, cela est plus cohérent avec logged_in_users et réduit énormément le risque de conflit
2 - détecter au moins le conflit et renommer l’utilisateur ou le groupe existant au lieu de générer une erreur

1 « J'aime »