Skip to content

Commit 254f1d8

Browse files
authored
Merge pull request rails#48357 from gmcgibbon/belongs_to_cpk
Add composite primary key validity check on belongs_to associations.
2 parents 5dd4dcb + f9a8f9c commit 254f1d8

File tree

8 files changed

+75
-4
lines changed

8 files changed

+75
-4
lines changed

activerecord/lib/active_record/associations.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,22 @@ def initialize(owner = nil, reflection = nil)
184184
end
185185
end
186186

187+
class CompositePrimaryKeyMismatchError < ActiveRecordError # :nodoc:
188+
attr_reader :reflection
189+
190+
def initialize(reflection = nil)
191+
if reflection
192+
if reflection.has_one? || reflection.collection?
193+
super("Association #{reflection.active_record}##{reflection.name} primary key #{reflection.active_record_primary_key} doesn't match with foreign key #{reflection.foreign_key}. Please specify query_constraints.")
194+
else
195+
super("Association #{reflection.active_record}##{reflection.name} primary key #{reflection.association_primary_key} doesn't match with foreign key #{reflection.foreign_key}. Please specify query_constraints.")
196+
end
197+
else
198+
super("Association primary key doesn't match with foreign key.")
199+
end
200+
end
201+
end
202+
187203
class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc:
188204
def initialize(klass, macro, association_name, options, possible_sources)
189205
example_options = options.dup

activerecord/lib/active_record/reflection.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,11 @@ def inverse_of
253253
end
254254

255255
def check_validity_of_inverse!
256-
unless polymorphic?
257-
if has_inverse? && inverse_of.nil?
256+
if !polymorphic? && has_inverse?
257+
if inverse_of.nil?
258258
raise InverseOfAssociationNotFoundError.new(self)
259259
end
260-
if has_inverse? && inverse_of == self
260+
if inverse_of == self
261261
raise InverseOfAssociationRecursiveError.new(self)
262262
end
263263
end
@@ -541,6 +541,14 @@ def join_foreign_key
541541

542542
def check_validity!
543543
check_validity_of_inverse!
544+
545+
if !polymorphic? && klass.composite_primary_key?
546+
if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
547+
raise CompositePrimaryKeyMismatchError.new(self)
548+
elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
549+
raise CompositePrimaryKeyMismatchError.new(self)
550+
end
551+
end
544552
end
545553

546554
def check_eager_loadable!

activerecord/test/cases/associations/belongs_to_associations_test.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
require "models/tree"
3131
require "models/node"
3232
require "models/club"
33+
require "models/cpk"
3334

3435
class BelongsToAssociationsTest < ActiveRecord::TestCase
3536
fixtures :accounts, :companies, :developers, :projects, :topics,
@@ -1745,6 +1746,18 @@ def self.name; "Temp"; end
17451746
ensure
17461747
ActiveRecord.belongs_to_required_validates_foreign_key = original_value
17471748
end
1749+
1750+
test "composite primary key malformed association" do
1751+
error = assert_raises(ActiveRecord::CompositePrimaryKeyMismatchError) do
1752+
book = Cpk::BrokenBook.new(title: "Some book", order: Cpk::Order.new(id: [1, 2]))
1753+
book.save!
1754+
end
1755+
1756+
assert_equal(<<~MESSAGE.squish, error.message)
1757+
Association Cpk::BrokenBook#order primary key ["shop_id", "id"]
1758+
doesn't match with foreign key order_id. Please specify query_constraints.
1759+
MESSAGE
1760+
end
17481761
end
17491762

17501763
class BelongsToWithForeignKeyTest < ActiveRecord::TestCase

activerecord/test/cases/associations/has_many_associations_test.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3174,6 +3174,18 @@ def test_key_ensuring_owner_was_is_valid_when_dependent_option_is_destroy_async
31743174
end
31753175
end
31763176

3177+
test "composite primary key malformed association" do
3178+
error = assert_raises(ActiveRecord::CompositePrimaryKeyMismatchError) do
3179+
order = Cpk::BrokenOrder.new(id: [1, 2], books: [Cpk::Book.new(title: "Some book")])
3180+
order.save!
3181+
end
3182+
3183+
assert_equal(<<~MESSAGE.squish, error.message)
3184+
Association Cpk::BrokenOrder#books primary key ["shop_id", "id"]
3185+
doesn't match with foreign key broken_order_id. Please specify query_constraints.
3186+
MESSAGE
3187+
end
3188+
31773189
private
31783190
def force_signal37_to_load_all_clients_of_firm
31793191
companies(:first_firm).clients_of_firm.load_target

activerecord/test/cases/associations/has_one_associations_test.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
require "models/club"
1919
require "models/membership"
2020
require "models/parrot"
21+
require "models/cpk"
2122

2223
class HasOneAssociationsTest < ActiveRecord::TestCase
2324
self.use_transactional_tests = false unless supports_savepoints?
@@ -902,4 +903,16 @@ def test_has_one_with_touch_option_on_nonpersisted_built_associations_doesnt_upd
902903
car.build_special_bulb
903904
end
904905
end
906+
907+
test "composite primary key malformed association" do
908+
error = assert_raises(ActiveRecord::CompositePrimaryKeyMismatchError) do
909+
order = Cpk::BrokenOrder.new(id: [1, 2], book: Cpk::Book.new(title: "Some book"))
910+
order.save!
911+
end
912+
913+
assert_equal(<<~MESSAGE.squish, error.message)
914+
Association Cpk::BrokenOrder#book primary key ["shop_id", "id"]
915+
doesn't match with foreign key broken_order_id. Please specify query_constraints.
916+
MESSAGE
917+
end
905918
end

activerecord/test/models/cpk/book.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ class Book < ActiveRecord::Base
55
self.table_name = :cpk_books
66
self.primary_key = [:author_id, :number]
77

8-
belongs_to :order
98
belongs_to :author, class_name: "Cpk::Author"
109
end
1110

1211
class BestSeller < Book
1312
end
13+
14+
class BrokenBook < Book
15+
belongs_to :order
16+
end
1417
end

activerecord/test/models/cpk/order.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ class Order < ActiveRecord::Base
66
self.primary_key = [:shop_id, :id]
77

88
has_many :order_agreements, primary_key: :id
9+
has_many :books, query_constraints: [:shop_id, :order_id]
10+
end
11+
12+
class BrokenOrder < Order
913
has_many :books
14+
has_one :book
1015
end
1116
end

activerecord/test/schema/schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@
246246
t.string :title
247247
t.integer :revision
248248
t.integer :order_id
249+
t.integer :shop_id
249250
end
250251

251252
create_table :cpk_authors, force: true do |t|

0 commit comments

Comments
 (0)