False positive for enum without DB column in rails 7.2 in parallelized tests
While updating an app to 7.2, my CI started throwing a lot of errors around enums:
RuntimeError: Undeclared attribute type for enum 'post_type' in Post. Enums must be backed by a database column or declared with an explicit type via `attribute`.
These are false positives as (1) the DB column exists (2) the tests worked locally/work when not executed in parallel.
I think the actual issue might not be related to enums, but just be some underlying problem in how attributes are initialized. However, I'm not knowledgeable enough about these rails internals to be really sure of what is going on
Steps to reproduce
- Add an enum backed by a Postgres enum to a model
- Add a test that calls the model in some way that loads the attribute info
- Run tests in parallel
- Run tests using
config.eager_load = true
(This is needed in my actual app but not in the reproduction below)
I had to add a bunch of stuff to the basic script to reproduce the error and had to create a separate schema file. There might be a better way to reproduce this
# frozen_string_literal: true
ENV['RAILS_ENV'] ||= 'test'
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
gem "rails", "~> 7.2.0"
# If you want to test against edge Rails replace the previous line with this:
# gem "rails", github: "rails/rails", branch: "main"
gem "pg", "~> 1.5.7"
gem "minitest", "5.24.1" # https://github.com/minitest/minitest/issues/1007
end
require "active_record"
require "action_controller/railtie"
require "logger"
ActiveRecord::Base.configurations = {
"test" => {
"adapter"=>"postgresql",
"database"=>"test",
"host"=>"localhost",
"username"=>"rails",
"password"=>"rails"
}
}
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(
adapter: "postgresql",
host: "localhost",
username: "rails",
password: "rails",
database: "test")
ActiveRecord::Base.logger = Logger.new(STDOUT)
class Application < Rails::Application
# Setting eager load to `true` matters in my test setup
# but this does not make a difference in the reproduction
config.eager_load = false
end
Rails.application.initialize!
class Post < ActiveRecord::Base
enum :post_type, { one: 'one', two: 'two', three: 'three' }
end
class ActiveSupport::TestCase
parallelize(
workers: 0, # The amount of workers doesn't matter, as long as `parallelize` is used
threshold: 1 # The threshold does matter, if you set this to 2 the tests will work
)
end
require "rails/test_help"
class BugTest < ActiveSupport::TestCase
def test_one
Post.new
assert true
end
def test_two
Post.new
assert true
end
end
In a separate db/schema.rb
file
ActiveRecord::Schema.define do
create_enum "my_enum", ["one", "two", "three"]
create_table :posts, force: true do |t|
t.enum :post_type, enum_type: :my_enum
end
end
Expected behavior
The DB column should be detected, since this worked in rails 7.1.X (and many versions before that)
Actual behavior
An error is throw saying that the enum does not have a matching DB column
RuntimeError: Undeclared attribute type for enum 'post_type' in Post. Enums must be backed by a database column or declared with an explicit type via `attribute`.
vendor/ruby/gems/activerecord-7.2.0/lib/active_record/enum.rb:259:in `block in _enum'
vendor/ruby/gems/activemodel-7.2.0/lib/active_model/attribute_registration.rb:71:in `block in apply_to'
vendor/ruby/gems/activemodel-7.2.0/lib/active_model/attribute_registration.rb:69:in `each'
vendor/ruby/gems/activemodel-7.2.0/lib/active_model/attribute_registration.rb:69:in `apply_to'
vendor/ruby/gems/activemodel-7.2.0/lib/active_model/attribute_registration.rb:87:in `block in apply_pending_attribute_modifications'
vendor/ruby/gems/activemodel-7.2.0/lib/active_model/attribute_registration.rb:86:in `each'
vendor/ruby/gems/activemodel-7.2.0/lib/active_model/attribute_registration.rb:86:in `apply_pending_attribute_modifications'
vendor/ruby/gems/activerecord-7.2.0/lib/active_record/attributes.rb:249:in `_default_attributes'
vendor/ruby/gems/activemodel-7.2.0/lib/active_model/attribute_registration.rb:38:in `attribute_types'
vendor/ruby/gems/activerecord-7.2.0/lib/active_record/attribute_methods.rb:260:in `_has_attribute?'
vendor/ruby/gems/activerecord-7.2.0/lib/active_record/inheritance.rb:61:in `new'
script.rb:67:in `test_one'
System configuration
Rails version: 7.2
Ruby version: 3.2.4