Db:seed_fu가 업그레이드 중 002_groups.rb에서 실패: Validation failed: Name has already been taken

v2026.3.0-latest에서 업그레이드하는 중 문제가 발생했습니다. rake db:seed_fu 태스크가 002_groups.rb에 도달하면 다음 오류와 함께 실패합니다:

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

크래시는 시더(seeder)가 새로운 시스템 그룹(ID 4: anonymousID 5: logged_in_users)을 초기화하려고 할 때 정확히 발생합니다.

Rails 콘솔을 통해 확인했습니다. 수동 확인은 유효성 검사에서 실패하지만, 유효성 검사를 우회하면 데이터베이스가 기록을 아무 문제 없이 받아들입니다:

# This fails with “Name has already been taken”
g = Group.new(id: 4, name: “anonymous”, automatic: true)
g.valid?

# This works fine, proving there’s no actual conflict
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라는 이름의 그룹 또는 사용자(!)가 존재할 때 이러한 문제가 발생합니다.

수많은 포럼에서 가져오기(import) 후 anonymous를 사용자 이름으로 사용했습니다.

해당 커밋에는

이 PR은 anonymous_users와 logged_in_users라는 두 개의 새로운 자동 그룹을 도입합니다

라고 언급되어 있지만, 결국 _users 접미사 없이 anonymous라는 이름의 그룹으로 변경된 것으로 보입니다.

이것은 아쉬운 부분입니다. 그 이유는 다음과 같습니다.

  • 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 의 다가오는 변경 로직과 관련된 몇 가지 문제도 수정합니다:

2개의 좋아요

처음에는 아니었습니다. 표준 재빌드 과정에서 마이그레이션 실행 시 실패하는 것이었고, 이것이 처음 제게 알려졌으며 문제의 원인을 추적했습니다. 이후 문제 해결을 위해 rake 작업을 수동으로 실행했습니다.

2개의 좋아요

네, 이제 PR을 병합했습니다. 이름 충돌 문제가 해결될 거예요.

1개의 좋아요

정말 좋은 소식입니다!

2026.5 버전에도 해당 수정 사항을 백포트(backport)해 주실 수 있을까요?

여기서 완료했습니다: DEV: Rename anonymous group to anonymous_users (#40435) - Pull Request #40629 - discourse/discourse - GitHub

1개의 좋아요