chronomodel icon indicating copy to clipboard operation
chronomodel copied to clipboard

`includes` does not work as expected in historical queries

Open tagliala opened this issue 8 months ago • 0 comments

Reproducible test case

# 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 :activities, temporal: true, force: true do |t|
    t.string :name
    t.timestamps
  end

  create_table :activity_roadmaps, temporal: true, force: true do |t|
    t.belongs_to :activity
    t.string :name

    t.timestamps
  end

  create_table :activity_steps, temporal: true, force: true do |t|
    t.belongs_to :activity_roadmap
    t.string :name
    t.integer :position, default: 1
    t.boolean :active, default: false

    t.timestamps
  end
end

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

  has_one :activity_roadmap, dependent: :destroy

  has_many :activity_steps, through: :activity_roadmap
  has_one :current_activity_step, through: :activity_roadmap

  scope :with_includes, lambda {
    includes(
      :current_activity_step,
      activity_roadmap: %i[activity_steps]
    )
  }
end

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

  belongs_to :activity

  has_many :activity_steps, -> { sorted }, dependent: :destroy, inverse_of: :activity_roadmap
  has_one :current_activity_step, -> { active.sorted }, class_name: 'ActivityStep', dependent: nil, inverse_of: false
end

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

  belongs_to :activity_roadmap

  scope :sorted, -> { order(:position) }
  scope :active, -> { where(active: true) }
end

activity = Activity.create!(name: 'Test')
activity_roadmap = activity.create_activity_roadmap!(name: 'Test')
activity_roadmap.activity_steps.create!(position: 1, active: false)
activity_roadmap.activity_steps.create!(position: 2, active: true)

class BugTest < Minitest::Test
  def test_temporal_includes
    assert Activity.with_includes.first
  end

  def test_historical_includes
    assert Activity.as_of(Time.now).with_includes.first
  end
end

Workarounds

    includes(
-     :current_activity_step,
-     activity_roadmap: :activity_steps
+     activity_roadmap: :activity_steps,
+     current_activity_step: []
    )

or

+   # Use `activity.current_activity_step` through the roadmap to take advantage of eager loading
    includes(
-     :current_activity_step,
-     activity_roadmap: :activity_steps
+     activity_roadmap: %i[activity_steps current_activity_step]
    )

tagliala avatar Jun 11 '24 09:06 tagliala