Skip to content

Commit 7716e32

Browse files
authored
Merge pull request rails#49113 from adrianna-chang-shopify/ac-infer-pk-id-composite-associations
Infer `primary_key: :id` on associations with composite primary key models
2 parents fee7de5 + 2fdd1ad commit 7716e32

File tree

9 files changed

+41
-15
lines changed

9 files changed

+41
-15
lines changed

activerecord/lib/active_record/autosave_association.rb

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,12 +449,16 @@ def save_has_one_association(reflection)
449449
if autosave && record.marked_for_destruction?
450450
record.destroy
451451
elsif autosave != false
452-
key = reflection.options[:primary_key] ? _read_attribute(reflection.options[:primary_key].to_s) : id
452+
primary_key = Array(compute_primary_key(reflection, self)).map(&:to_s)
453+
primary_key_value = primary_key.map { |key| _read_attribute(key) }
453454

454-
if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, key)
455+
if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, primary_key_value)
455456
unless reflection.through_reflection
456-
Array(key).zip(Array(reflection.foreign_key)).each do |primary_key, foreign_key_column|
457-
record[foreign_key_column] = primary_key
457+
foreign_key = Array(reflection.foreign_key)
458+
primary_key_foreign_key_pairs = primary_key.zip(foreign_key)
459+
460+
primary_key_foreign_key_pairs.each do |primary_key, foreign_key|
461+
record[foreign_key] = _read_attribute(primary_key)
458462
end
459463
association.set_inverse_instance(record)
460464
end
@@ -534,6 +538,10 @@ def compute_primary_key(reflection, record)
534538
query_constraints
535539
elsif record.class.has_query_constraints? && !reflection.options[:foreign_key]
536540
record.class.query_constraints_list
541+
elsif record.class.composite_primary_key?
542+
# If record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
543+
primary_key = record.class.primary_key
544+
primary_key.include?("id") ? "id" : primary_key
537545
else
538546
record.class.primary_key
539547
end

activerecord/lib/active_record/reflection.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,6 @@ def join_table
500500

501501
def foreign_key(infer_from_inverse_of: true)
502502
@foreign_key ||= if options[:query_constraints]
503-
# composite foreign keys support
504503
options[:query_constraints].map { |fk| fk.to_s.freeze }.freeze
505504
elsif options[:foreign_key]
506505
options[:foreign_key].to_s
@@ -533,6 +532,10 @@ def active_record_primary_key
533532
end
534533
elsif active_record.has_query_constraints? || options[:query_constraints]
535534
active_record.query_constraints_list
535+
elsif active_record.composite_primary_key?
536+
# If active_record has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
537+
primary_key = primary_key(active_record)
538+
primary_key.include?("id") ? "id" : primary_key.freeze
536539
else
537540
primary_key(active_record).freeze
538541
end
@@ -863,6 +866,10 @@ def association_primary_key(klass = nil)
863866
(klass || self.klass).composite_query_constraints_list
864867
elsif primary_key = options[:primary_key]
865868
@association_primary_key ||= -primary_key.to_s
869+
elsif (klass || self.klass).composite_primary_key?
870+
# If klass has composite primary key of shape [:<tenant_key>, :id], infer primary_key as :id
871+
primary_key = (klass || self.klass).primary_key
872+
primary_key.include?("id") ? "id" : primary_key
866873
else
867874
primary_key(klass || self.klass)
868875
end

activerecord/test/cases/associations/belongs_to_associations_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1784,7 +1784,7 @@ def self.name; "Temp"; end
17841784
end
17851785

17861786
assert_equal(<<~MESSAGE.squish, error.message)
1787-
Association Cpk::BrokenBook#order primary key ["shop_id", "id"]
1787+
Association Cpk::BrokenBook#order primary key ["shop_id", "status"]
17881788
doesn't match with foreign key order_id. Please specify query_constraints, or primary_key and foreign_key values.
17891789
MESSAGE
17901790
end

activerecord/test/cases/associations/has_many_associations_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3199,7 +3199,7 @@ def test_key_ensuring_owner_was_is_valid_when_dependent_option_is_destroy_async
31993199
end
32003200

32013201
assert_equal(<<~MESSAGE.squish, error.message)
3202-
Association Cpk::BrokenOrder#books primary key ["shop_id", "id"]
3202+
Association Cpk::BrokenOrder#books primary key ["shop_id", "status"]
32033203
doesn't match with foreign key broken_order_id. Please specify query_constraints, or primary_key and foreign_key values.
32043204
MESSAGE
32053205
end
@@ -3211,7 +3211,7 @@ def test_key_ensuring_owner_was_is_valid_when_dependent_option_is_destroy_async
32113211
end
32123212

32133213
assert_equal(<<~MESSAGE.squish, error.message)
3214-
Association Cpk::BrokenOrderWithNonCpkBooks#books primary key [\"shop_id\", \"id\"]
3214+
Association Cpk::BrokenOrderWithNonCpkBooks#books primary key [\"shop_id\", \"status\"]
32153215
doesn't match with foreign key broken_order_with_non_cpk_books_id. Please specify query_constraints, or primary_key and foreign_key values.
32163216
MESSAGE
32173217
end

activerecord/test/cases/associations/has_one_associations_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -922,7 +922,7 @@ def test_has_one_with_touch_option_on_nonpersisted_built_associations_doesnt_upd
922922
end
923923

924924
assert_equal(<<~MESSAGE.squish, error.message)
925-
Association Cpk::BrokenOrder#book primary key ["shop_id", "id"]
925+
Association Cpk::BrokenOrder#book primary key ["shop_id", "status"]
926926
doesn't match with foreign key broken_order_id. Please specify query_constraints, or primary_key and foreign_key values.
927927
MESSAGE
928928
end
@@ -934,7 +934,7 @@ def test_has_one_with_touch_option_on_nonpersisted_built_associations_doesnt_upd
934934
end
935935

936936
assert_equal(<<~MESSAGE.squish, error.message)
937-
Association Cpk::BrokenOrderWithNonCpkBooks#book primary key [\"shop_id\", \"id\"]
937+
Association Cpk::BrokenOrderWithNonCpkBooks#book primary key [\"shop_id\", \"status\"]
938938
doesn't match with foreign key broken_order_with_non_cpk_books_id. Please specify query_constraints, or primary_key and foreign_key values.
939939
MESSAGE
940940
end

activerecord/test/models/cpk/book.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ class BestSeller < Book
1313
end
1414

1515
class BrokenBook < Book
16-
belongs_to :order
16+
belongs_to :order, class_name: "Cpk::OrderWithSpecialPrimaryKey"
1717
end
1818

1919
class BrokenBookWithNonCpkOrder < Book

activerecord/test/models/cpk/order.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,28 @@ class Order < ActiveRecord::Base
99

1010
alias_attribute :id_value, :id
1111

12-
has_many :order_agreements, primary_key: :id
12+
has_many :order_agreements
1313
has_many :books, query_constraints: [:shop_id, :order_id]
1414
has_one :book, query_constraints: [:shop_id, :order_id]
1515
end
1616

1717
class BrokenOrder < Order
18+
self.primary_key = [:shop_id, :status]
19+
1820
has_many :books
1921
has_one :book
2022
end
2123

24+
class OrderWithSpecialPrimaryKey < Order
25+
self.primary_key = [:shop_id, :status]
26+
27+
has_many :books, query_constraints: [:shop_id, :status]
28+
has_one :book, query_constraints: [:shop_id, :status]
29+
end
30+
2231
class BrokenOrderWithNonCpkBooks < Order
32+
self.primary_key = [:shop_id, :status]
33+
2334
has_many :books, class_name: "Cpk::NonCpkBook"
2435
has_one :book, class_name: "Cpk::NonCpkBook"
2536
end
@@ -29,7 +40,7 @@ class NonCpkOrder < Order
2940
end
3041

3142
class OrderWithPrimaryKeyAssociatedBook < Order
32-
has_one :book, primary_key: :id, foreign_key: :order_id
43+
has_one :book, foreign_key: :order_id
3344
end
3445

3546
class OrderWithNullifiedBook < Order

activerecord/test/models/cpk/order_agreement.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ module Cpk
55
class OrderAgreement < ActiveRecord::Base
66
self.table_name = :cpk_order_agreements
77

8-
belongs_to :order, primary_key: :id # foreign key is derived as `order_id`
8+
belongs_to :order
99
end
1010
end

activerecord/test/models/cpk/order_tag.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ class OrderTag < ActiveRecord::Base
55
self.table_name = :cpk_order_tags
66

77
belongs_to :tag
8-
belongs_to :order, primary_key: :id
8+
belongs_to :order
99
end
1010
end

0 commit comments

Comments
 (0)