Skip to content

Commit 7d7ed37

Browse files
authored
Merge pull request rails#47534 from Shopify/fix-polymorphic-has-many-through-assignments
Fix assigning through records when using polymorphic has many through association
2 parents 1b10f5c + c12a77d commit 7d7ed37

File tree

6 files changed

+33
-3
lines changed

6 files changed

+33
-3
lines changed

activerecord/lib/active_record/associations/has_many_through_association.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,10 @@ def build_through_record(record)
5959

6060
attributes = through_scope_attributes
6161
attributes[source_reflection.name] = record
62-
attributes[source_reflection.foreign_type] = options[:source_type] if options[:source_type]
6362

64-
through_association.build(attributes)
63+
through_association.build(attributes).tap do |new_record|
64+
new_record.send("#{source_reflection.foreign_type}=", options[:source_type]) if options[:source_type]
65+
end
6566
end
6667
end
6768

activerecord/test/cases/reflection_test.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
require "models/drink_designer"
2828
require "models/recipe"
2929
require "models/user_with_invalid_relation"
30+
require "models/hardback"
3031

3132
class ReflectionTest < ActiveRecord::TestCase
3233
include ActiveRecord::Reflection
@@ -324,7 +325,7 @@ def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case
324325
assert_equal 2, hotel.chefs.count
325326
end
326327

327-
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_sti
328+
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_subclass_source
328329
hotel = Hotel.create!
329330
hotel.mocktail_designers << MocktailDesigner.create!
330331

@@ -341,6 +342,20 @@ def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_case_and_sti
341342
assert_equal 0, hotel.chef_lists.count
342343
end
343344

345+
def test_scope_chain_does_not_interfere_with_hmt_with_polymorphic_and_subclass_source_2
346+
author = Author.create!(name: "John Doe")
347+
hardback = BestHardback.create!
348+
author.best_hardbacks << hardback
349+
350+
assert_equal [hardback], author.best_hardbacks
351+
assert_equal [hardback], author.reload.best_hardbacks
352+
353+
author.best_hardbacks = []
354+
355+
assert_empty author.best_hardbacks
356+
assert_empty author.reload.best_hardbacks
357+
end
358+
344359
def test_scope_chain_of_polymorphic_association_does_not_leak_into_other_hmt_associations
345360
hotel = Hotel.create!
346361
department = hotel.departments.create!

activerecord/test/models/author.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ def ratings
168168
has_many :tags_with_primary_key, through: :posts
169169

170170
has_many :books
171+
has_many :best_hardbacks, through: :books, source: :format_record, source_type: "BestHardback"
171172
has_many :published_books, class_name: "PublishedBook"
172173
has_many :unpublished_books, -> { where(status: [:proposed, :written]) }, class_name: "Book"
173174
has_many :subscriptions, through: :books

activerecord/test/models/book.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
class Book < ActiveRecord::Base
44
belongs_to :author
5+
belongs_to :format_record, polymorphic: true
56

67
has_many :citations, foreign_key: "book1_id", inverse_of: :book
78
has_many :references, -> { distinct }, through: :citations, source: :reference_of

activerecord/test/models/hardback.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# frozen_string_literal: true
2+
3+
class Hardback < ActiveRecord::Base
4+
end
5+
6+
class BestHardback < Hardback
7+
end

activerecord/test/schema/schema.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@
125125
default_zero = { default: 0 }
126126
t.references :author
127127
t.string :format
128+
t.integer :format_record_id
129+
t.string :format_record_type
128130
t.column :name, :string
129131
t.column :status, :integer, **default_zero
130132
t.column :last_read, :integer, **default_zero
@@ -165,6 +167,9 @@
165167
t.datetime :updated_at
166168
end
167169

170+
create_table :hardbacks, force: true do |t|
171+
end
172+
168173
create_table :booleans, force: true do |t|
169174
t.boolean :value
170175
t.boolean :has_fun, null: false, default: false

0 commit comments

Comments
 (0)