Last active
October 13, 2021 17:57
-
-
Save composerinteralia/40b4c703d725551e7309f83b4d033083 to your computer and use it in GitHub Desktop.
Bug with multiple databases, through association, and after_commit callback
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", path: "/Users/composerinteralia/code/rails/rails" | |
gem "sqlite3" | |
end | |
require "active_record" | |
require "minitest/autorun" | |
require "logger" | |
db = Tempfile.new(['db', '.db']) | |
db2 = Tempfile.new(['db2', '.db']) | |
ActiveRecord::Base.configurations = { issues: { adapter: "sqlite3", database: db2 } } | |
ActiveRecord::Base.legacy_connection_handling = false | |
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: db) | |
ActiveRecord::Base.establish_connection(:issues) | |
ActiveRecord::Base.logger = Logger.new(STDOUT) | |
class Issues < ActiveRecord::Base | |
self.abstract_class = true | |
# The test passes if we comment this out so everything is in one database | |
connects_to database: { writing: :issues, reading: :issues } | |
end | |
ActiveRecord::Schema.define do | |
create_table :assignees, force: true do |t| | |
end | |
end | |
Issues.connection.execute('CREATE TABLE "issues" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL)') | |
Issues.connection.execute(<<~SQL) | |
CREATE TABLE "assignments" ( | |
"id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, | |
"issue_id" integer, | |
"assignee_id" integer | |
) | |
SQL | |
class Issue < Issues | |
# The test also passes if we set `inverse_of: false` here since we'll end up with a new issue object and load the assignees on that instead | |
has_many :assignments | |
has_many :assignees, through: :assignments, disable_joins: true | |
end | |
class Assignment < Issues | |
belongs_to :issue | |
belongs_to :assignee | |
after_commit :load_issue_assignees | |
def load_issue_assignees | |
issue.assignees.to_a | |
end | |
end | |
class Assignee < ActiveRecord::Base | |
has_many :assignments | |
end | |
class BugTest < Minitest::Test | |
def test_association_stuff | |
issue = Issue.create! | |
user = Assignee.create | |
# When the models are in different databases, the after commit runs earlier | |
# (presumably because a transaction from one database won't be joinable from | |
# another) and so the `assignees` association gets loaded, but then we add | |
# this new assignee to the association target even though it's already there | |
# at https://github.com/rails/rails/blob/ced387ee58e9fd980217dd1f472f2c5afa241e9a/activerecord/lib/active_record/associations/collection_association.rb#L467-L470 | |
# | |
# When the models are in the same database, this happens in the opposite | |
# order because the after_commit runs later. We add the new assignee to | |
# the association target, then later the after_commit runs and loads the | |
# whole association. | |
issue.assignees << user | |
assert_equal [user], issue.assignees.to_a | |
# expected: [#<Assignee id: 1>] | |
# actual: [#<Assignee id: 1>, #<Assignee id: 1>] | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment