Last active
October 5, 2021 16:43
-
-
Save composerinteralia/d8fb4c83394be31aa816b77f287cc5ad to your computer and use it in GitHub Desktop.
Active Record counter_cache + inverse_of bug
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 :posts, force: true do |t| | |
t.integer :comments_count, default: 0 | |
end | |
create_table :comments, force: true do |t| | |
t.integer :post_id | |
end | |
end | |
class Post < ActiveRecord::Base | |
has_many :comments | |
# Tests pass if we disable inverse_of: | |
# has_many :comments, inverse_of: false | |
end | |
class Comment < ActiveRecord::Base | |
belongs_to :post, counter_cache: true | |
end | |
class BugTest < Minitest::Test | |
def test_association_stuff | |
post = Post.create! | |
other_post = Post.create! | |
comment = Comment.create! | |
assert_equal 0, post.comments_count | |
assert_equal 0, other_post.comments_count | |
post.comments << comment | |
assert_equal 1, post.reload.comments_count | |
comment.update(post_id: other_post.id) | |
# These fail if the comments association inverse_of set. | |
# That is because `stale_target?` returns `false` when the target was set | |
# via `inverse_of`, and so `BelongsToAssociation#update_counters` ends up | |
# incrementing and then decrementing the `comments_count` on the original | |
# target, rather than incrementing on the new target and decrementing on the | |
# old one. | |
assert_equal 0, post.reload.comments_count | |
assert_equal 1, other_post.reload.comments_count | |
end | |
end | |
# Rails tests this behavior, but happens to do it using associations where automatic inverse_of is disabled because of a custom foreign_key: | |
# https://github.com/rails/rails/blob/main/activerecord/test/cases/associations/has_many_associations_test.rb#L1443-L1459 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment