chronomodel icon indicating copy to clipboard operation
chronomodel copied to clipboard

Unexpected results with joins and eager loading

Open tagliala opened this issue 2 years ago • 0 comments

# frozen_string_literal: true

require 'bundler/inline'

gemfile(true) do
  source 'https://rubygems.org'

  gem 'chrono_model'
  # Test against latest Chronomodel:
  # gem 'chrono_model', github: 'ifad/chronomodel'

  gem 'pg'
  gem 'debug'
  gem 'rails'
end

require 'chrono_model'
require 'minitest/autorun'
require 'logger'
require 'debug'

# Needs a database called `chronomodel_test`

ActiveRecord::Base.establish_connection(adapter: 'chronomodel', database: 'chronomodel_test')
ActiveRecord::Base.logger = Logger.new($stdout)

ActiveRecord::Schema.define do
  enable_extension :btree_gist

  create_table :countries, force: true, temporal: true do |t|
    t.string :name
    t.timestamps
  end

  create_table :cities, force: true, temporal: true do |t|
    t.references :country
    t.string :name
  end

  create_table :schools, force: true, temporal: true do |t|
    t.references :city

    t.string :name

    t.timestamps
  end
end

class Country < ActiveRecord::Base
  include ChronoModel::TimeMachine

  has_many :cities, dependent: :destroy
  has_many :schools, through: :cities

  validates :name, presence: true
end

class City < ActiveRecord::Base
  include ChronoModel::TimeMachine

  validates :name, presence: true

  belongs_to :country

  has_many :schools, dependent: :destroy
end

class School < ActiveRecord::Base
  include ChronoModel::TimeMachine

  belongs_to :city
  has_one :country, through: :city

  validates :name, presence: true
end

ITALY = Country.create!(name: 'Italy')

rome = ITALY.cities.create!(name: 'Rome')
rome.schools.create!(name: 'G. Galilei')
rome.schools.create!(name: 'L. Da Vinci')

sleep 2

ITALY.update name: 'Italia'

class BugTest < Minitest::Test
  def test_joins
    assert_equal ['Italy'], School.as_of(ITALY.updated_at - 1.second).joins(:city).map { |s| s.city.country.name }.uniq
  end

  def test_includes
    assert_equal ['Italy'], School.as_of(ITALY.updated_at - 1.second).joins(:city).map { |s| s.city.country.name }.uniq
  end

  def test_includes_left_outer_joins
    assert_equal ['Italy'], School.includes(:city).as_of(ITALY.updated_at - 1.second).left_outer_joins(:city).map { |s| s.city.country.name }.uniq
  end

  def test_includes_joins
    assert_equal ['Italy'], School.includes(:city).as_of(ITALY.updated_at - 1.second).joins(:city).map { |s| s.city.country.name }.uniq
  end
end

Tests:

joins

# Expected: Italy, Got: Italy
School.as_of(italy.updated_at - 1.second).joins(:city).map { |s| s.city.country.name }.uniq

includes

# Expected: Italy, Got: Italy
School.includes(:city).as_of(italy.updated_at - 1.second).map { |s| s.city.country.name }.uniq

includes + left_outer_joins

# Expected: Italy, Got: Italy
School.includes(:city).as_of(italy.updated_at - 1.second).left_outer_joins(:city).map { |s| s.city.country.name }.uniq

includes + joins

# Expected: Italy, Got: Italia
School.includes(:city).as_of(italy.updated_at - 1.second).joins(:city).map { |s| s.city.country.name }.uniq

tagliala avatar Oct 25 '22 10:10 tagliala