chronomodel
chronomodel copied to clipboard
Unexpected results with joins and eager loading
# 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