ActiveRecord preload from Rails 7 does not work with polymorphic relations
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
There's a regression in rails 7 that returns the wrong record type for STIs(without type SQL conditions):
require "bundler/inline"
gemfile(true, quiet: true) do
source "https://rubygems.org"
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
if ENV.key?('RAILS_MAIN')
gem "rails", github: "rails/rails", branch: "main"
elsif ENV.key?('RAILS7')
gem 'rails', '~> 7.0', '>= 7.0.4'
else
gem 'rails', '~> 6.1', '>= 6.1.7'
end
gem 'sqlite3'
end
require "active_record"
require "logger"
require "minitest/autorun"
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
create_table :todos, force: true do |t|
t.integer :target_id
t.string :target_type
end
create_table :issues, force: true do |t|
end
end
class Todo < ActiveRecord::Base
belongs_to :target, polymorphic: true, touch: true
end
class Issue < ActiveRecord::Base
has_many :todos, as: :target
end
class WorkItem < Issue
self.table_name = 'issues'
self.inheritance_column = :_type_disabled
end
class BugTest < Minitest::Test
def test_preload
issue = Issue.create
item = WorkItem.create
Todo.create(target: issue)
Todo.create(target_id: item.id, target_type: WorkItem.name)
result = Todo.preload(:target).map(&:target)
assert_equal [issue, item], result
end
end
Rails 6
marius@rocket-sled ~/W/s/rails7 (rails-6-preload-patch) [1]> ruby issues.rb
Run options: --seed 25091
# Running:
D, [2022-12-14T16:01:31.495061 #66111] DEBUG -- : Issue Load (0.1ms) SELECT "issues".* FROM "issues" WHERE "issues"."id" = ? [["id", 1]]
D, [2022-12-14T16:01:31.495305 #66111] DEBUG -- : WorkItem Load (0.0ms) SELECT "issues".* FROM "issues" WHERE "issues"."id" = ? [["id", 2]]
.
Finished in 0.011108s, 90.0252 runs/s, 90.0252 assertions/s.
1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
Rails 7
marius@rocket-sled ~/W/s/rails7 (rails-6-preload-patch)> RAILS7=1 ruby issues.rb
Run options: --seed 43623
# Running:
D, [2022-12-14T16:01:39.529942 #66669] DEBUG -- : Todo Load (0.0ms) SELECT "todos".* FROM "todos"
D, [2022-12-14T16:01:39.533679 #66669] DEBUG -- : Issue Load (0.1ms) SELECT "issues".* FROM "issues" WHERE "issues"."id" IN (?, ?) [["id", 1], ["id", 2]]
F
Failure:
BugTest#test_preload [issues.rb:58]:
--- expected
+++ actual
@@ -1 +1 @@
-[#<Issue id: 1>, #<WorkItem id: 2>]
+[#<Issue id: 1>, #<Issue id: 2>]
Finished in 0.028499s, 35.0890 runs/s, 35.0890 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
Rails main
marius@rocket-sled ~/W/s/rails7 (rails-6-preload-patch) [1]> RAILS_MAIN=1 ruby issues.rb
Run options: --seed 2328
# Running:
D, [2022-12-14T16:01:58.540472 #67733] DEBUG -- : TRANSACTION (0.0ms) commit transaction
D, [2022-12-14T16:01:58.540648 #67733] DEBUG -- : Todo Load (0.0ms) SELECT "todos".* FROM "todos"
D, [2022-12-14T16:01:58.543596 #67733] DEBUG -- : Issue Load (0.0ms) SELECT "issues".* FROM "issues" WHERE "issues"."id" IN (?, ?) [["id", 1], ["id", 2]]
F
Failure:
BugTest#test_preload [issues.rb:58]:
--- expected
+++ actual
@@ -1 +1 @@
-[#<Issue id: 1>, #<WorkItem id: 2>]
+[#<Issue id: 1>, #<Issue id: 2>]
Finished in 0.029127s, 34.3324 runs/s, 34.3324 assertions/s.
1 runs, 1 assertions, 1 failures, 0 errors, 0 skips
It returns the model that it's encountered first, for example:
class BugTest < Minitest::Test
def test_preload
issue = Issue.create
item = WorkItem.create
Todo.create(target_id: item.id, target_type: WorkItem.name) # these 2 lines are flipped
Todo.create(target: issue) #
result = Todo.preload(:target).map(&:target)
assert_equal [issue, item], result
end
end
Failure:
BugTest#test_preload [issues.rb:58]:
--- expected
+++ actual
@@ -1 +1 @@
-[#<Issue id: 1>, #<WorkItem id: 2>]
+[#<WorkItem id: 2>, #<WorkItem id: 1>]
I think it's similar to https://github.com/rails/rails/issues/45056.
It's failing in https://gitlab.com/gitlab-org/gitlab/-/jobs/3466325323 where we have todos for both issues and work items.
Edited by 🤖 GitLab Bot 🤖