rails icon indicating copy to clipboard operation
rails copied to clipboard

ActiveRecord: HMT with conditions does not update the in-memory collection when pushing new objects

Open joanniclaborde opened this issue 1 year ago • 0 comments

Hi! When using a has_many :a, through: :b relation, and the :b relation has a condition, adding new objects doesn't update the in-memory collection properly (stops after the first object).

Steps to reproduce

# frozen_string_literal: true

require "bundler/inline"

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

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "rails", github: "rails/rails", branch: "main"
  gem "sqlite3"
end

require "active_record"
require "minitest/autorun"
require "logger"

# This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :packages, force: true do |t|
  end

  create_table :package_items, force: true do |t|
    t.integer :package_id
    t.integer :item_id
  end

  create_table :items, force: true do |t|
    t.boolean "deleted", default: false
  end
end

class Package < ActiveRecord::Base
  has_many :package_items, ->{ where(item_id: Item.active.pluck(:id)) }
  has_many :items, through: :package_items
end

class PackageItem < ActiveRecord::Base
  belongs_to :package
  belongs_to :item
end

class Item < ActiveRecord::Base
  has_many :package_items
  has_many :packages, through: :package_items

  scope :active, -> { where(deleted: false) }
end

class BugTest < Minitest::Test
  def test_association_stuff
    package = Package.create!
    package.items << Item.create!
    package.items << Item.create!
    package.items << Item.create!

    # This should be [3, 3, 3] but we get [3, 1, 3]
    assert_equal [3, 3, 3], [Item.count, package.items.count, package.reload.items.count]
  end
end

Expected behavior

package.items.count is equal to 3.

Actual behavior

package.items.count is equal to 1, even though all the items and relations are created properly (see assertion).

System configuration

Rails version: tested on 7.1.0.alpha and 6.1.6.1.

Ruby version: tested on 2.7.5.

Workaround

I was able to fix this issue by changing the weird condition to a proper scope:

class Package < ActiveRecord::Base
  has_many :package_items, ->{ includes(:item).merge(Item.active) }
  has_many :items, through: :package_items
end

joanniclaborde avatar Aug 26 '22 22:08 joanniclaborde