-
Notifications
You must be signed in to change notification settings - Fork 21.9k
Closed
Labels
Description
Steps to reproduce
I have written a test to reproduce the error. Passing on 6.0 and failing on 6.1.
Put this file in its own directory and run it from there.
# If you change the gem dependencies, run it with:
# `rm Gemfile* && ruby test.rb`
unless File.exist?("Gemfile")
File.write("Gemfile", <<-GEMFILE)
source "https://rubygems.org"
# gem "rails", github: "rails/rails", branch: "6-0-stable"
gem "rails", github: "rails/rails", branch: "6-1-stable"
gem "pg"
GEMFILE
system "bundle install"
end
require "bundler"
Bundler.setup(:default)
require "active_record"
require "minitest/autorun"
require "logger"
# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(
adapter: "postgresql",
database: "activerecord_test",
host: "postgres",
user: "username",
password: "password"
)
# ActiveRecord::Base.logger = Logger.new(STDOUT)
# Display versions.
message = "Running test case with Ruby #{RUBY_VERSION}, Active Record #{
::ActiveRecord::VERSION::STRING}, Arel #{Arel::VERSION} and #{
::ActiveRecord::Base.connection.adapter_name}"
line = "=" * message.length
puts line, message, line
ActiveRecord::Schema.define do
create_table :contacts, force: true do |t|
t.string :name
end
create_table :contact_relationships, force: true do |t|
t.references :child
t.references :parent
t.string :relationship_type
end
end
class Contact < ActiveRecord::Base
has_one :commune_relationship, -> { where(relationship_type: "commune") }, class_name: "ContactRelationship", foreign_key: :child_id
has_one :commune, source: :parent, through: :commune_relationship
has_many :commune_for_relationships, -> { where(relationship_type: "commune") }, class_name: "ContactRelationship", foreign_key: :parent_id
has_many :commune_for, source: :child, through: :commune_for_relationships
has_many :schools_relationships, -> { where(relationship_type: "school") }, class_name: "ContactRelationship", foreign_key: :child_id
has_many :schools, source: :parent, through: :schools_relationships
has_many :schools_communes, source: :commune, through: :schools
has_many :school_for_relationships, -> { where(relationship_type: "school") }, class_name: "ContactRelationship", foreign_key: :parent_id
has_many :school_for, source: :child, through: :school_for_relationships
has_many :children_relationships, class_name: "ContactRelationship", foreign_key: :parent_id
has_many :children, source: :child, through: :children_relationships
has_many :parents_relationships, class_name: "ContactRelationship", foreign_key: :child_id
has_many :parents, source: :parent, through: :parents_relationships
end
class ContactRelationship < ActiveRecord::Base
belongs_to :child, class_name: "Contact"
belongs_to :parent, class_name: "Contact"
end
class TestActiveRecord < ActiveSupport::TestCase
def setup
@nothern_commune = Contact.create!(name: "Nothern Commune")
@southern_commune = Contact.create!(name: "Southern Commune")
@southern_school = Contact.create!(name: "Southern School")
ContactRelationship.create!(child: @southern_school, parent: @southern_commune, relationship_type: "commune")
@nothern_school = Contact.create!(name: "Nothern School")
ContactRelationship.create!(child: @nothern_school, parent: @nothern_commune, relationship_type: "commune")
# Student that both lives and goes to school in the north
@nothern_student = Contact.create!(name: "Nothern Student")
ContactRelationship.create!(child: @nothern_student, parent: @nothern_commune, relationship_type: "commune")
ContactRelationship.create!(child: @nothern_student, parent: @nothern_school, relationship_type: "school")
# Student that both lives and goes to school in the south
@southern_student = Contact.create!(name: "Southern Student")
ContactRelationship.create!(child: @southern_student, parent: @southern_commune, relationship_type: "commune")
ContactRelationship.create!(child: @southern_student, parent: @southern_school, relationship_type: "school")
# Student that lives in the northern commune but goes to school in the southern school
@eastern_student = Contact.create!(name: "Eastern Student")
ContactRelationship.create!(child: @eastern_student, parent: @nothern_commune, relationship_type: "commune")
ContactRelationship.create!(child: @eastern_student, parent: @southern_school, relationship_type: "school")
# Student that liaves in the southern commune but goes to school in the nothern school
@western_student = Contact.create!(name: "Western Student")
ContactRelationship.create!(child: @western_student, parent: @southern_commune, relationship_type: "commune")
ContactRelationship.create!(child: @western_student, parent: @nothern_school, relationship_type: "school")
end
def test_setup
assert_equal @nothern_commune, @nothern_student.commune
assert_equal [@nothern_school], @nothern_student.schools
assert_equal [@nothern_commune], @nothern_student.schools_communes
assert_equal @southern_commune, @southern_student.commune
assert_equal [@southern_school], @southern_student.schools
assert_equal [@southern_commune], @southern_student.schools_communes
assert_equal @nothern_commune, @eastern_student.commune
assert_equal [@southern_school], @eastern_student.schools
assert_equal [@southern_commune], @eastern_student.schools_communes
assert_equal @southern_commune, @western_student.commune
assert_equal [@nothern_school], @western_student.schools
assert_equal [@nothern_school, @nothern_student, @eastern_student], @nothern_commune.children
assert_equal [@nothern_school, @nothern_student, @eastern_student], @nothern_commune.commune_for
assert_equal [@southern_school, @southern_student, @western_student], @southern_commune.children
assert_equal [@southern_school, @southern_student, @western_student], @southern_commune.commune_for
assert_equal [@nothern_student, @western_student], @nothern_school.children
assert_equal [@nothern_student, @western_student], @nothern_school.school_for
assert_equal [@southern_student, @eastern_student], @southern_school.school_for
end
def test_queries
query = Contact
.left_joins(:commune, schools: :commune)
.where("communes_contacts.id = :commune_id OR communes_contacts_2.id = :commune_id", commune_id: @southern_commune.id)
# Output from Rails 6.0
# SELECT "contacts".*
# FROM "contacts"
# LEFT OUTER JOIN "contact_relationships" ON "contact_relationships"."relationship_type" = 'commune' AND "contact_relationships"."child_id" = "contacts"."id"
# LEFT OUTER JOIN "contacts" "communes_contacts" ON "communes_contacts"."id" = "contact_relationships"."parent_id"
# LEFT OUTER JOIN "contact_relationships" "schools_relationships_contacts_join" ON "schools_relationships_contacts_join"."relationship_type" = 'school' AND "schools_relationships_contacts_join"."child_id" = "contacts"."id"
# LEFT OUTER JOIN "contacts" "schools_contacts" ON "schools_contacts"."id" = "schools_relationships_contacts_join"."parent_id"
# Note: This relationships-join is missing in Rails 6.1
# LEFT OUTER JOIN "contact_relationships" "commune_relationships_contacts_join" ON "commune_relationships_contacts_join"."relationship_type" = 'commune' AND "commune_relationships_contacts_join"."child_id" = "schools_contacts"."id"
# LEFT OUTER JOIN "contacts" "communes_contacts_2" ON "communes_contacts_2"."id" = "commune_relationships_contacts_join"."parent_id"
# WHERE (communes_contacts.id = 2 OR communes_contacts_2.id = 2)
# Output from Rails 6.1
# SELECT "contacts".*
# FROM "contacts"
# LEFT OUTER JOIN "contact_relationships" ON "contact_relationships"."relationship_type" = 'commune' AND "contact_relationships"."child_id" = "contacts"."id"
# LEFT OUTER JOIN "contacts" "communes_contacts" ON "communes_contacts"."id" = "contact_relationships"."parent_id"
# LEFT OUTER JOIN "contact_relationships" "schools_relationships_contacts_join" ON "schools_relationships_contacts_join"."relationship_type" = 'school' AND "schools_relationships_contacts_join"."child_id" = "contacts"."id"
# LEFT OUTER JOIN "contacts" "schools_contacts" ON "schools_contacts"."id" = "schools_relationships_contacts_join"."parent_id"
# Note: Missing relationships join like in Rails 6.0
# LEFT OUTER JOIN "contacts" "communes_contacts_2" ON "communes_contacts_2"."id" = "contact_relationships"."parent_id"
# WHERE (communes_contacts.id = 2 OR communes_contacts_2.id = 2)
# puts "SQL:\n#{query.to_sql}"
# Southern school should be included because it belongs to the southern commune
# Southern student should be included because he both goes to school and lives in the south
# Eastern student should be included because he goes to school in the southern school that belongs to the southern commune
# Western student should be included because he lives in the south and is directly related to the southern commune
assert_equal [@southern_school, @southern_student, @eastern_student, @western_student], query
end
end
Expected behavior
I would expect the "contact_relationships" for communes on schools to be present instead of re-using the previous join wrongly.
I would expect this assertion to pass:
assert_equal [@southern_school, @southern_student, @eastern_student, @western_student], query
Actual behavior
It uses the previous join wrongly and reaches the wrong result.
System configuration
Rails version: 6.1
Ruby version: 2.6.6
ozzyaaron, fn-reflection, SimonBrazell, kjleitz, DCrow and 5 more