Skip to content

Commit 33039fa

Browse files
authored
Merge pull request rails#26718 from domcleal/5-0-stable-ids-writer-exception
Restore RecordNotFound when *_ids= can't find records by ID
2 parents 2f73982 + 9355020 commit 33039fa

File tree

5 files changed

+40
-8
lines changed

5 files changed

+40
-8
lines changed

activerecord/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
* Raise ActiveRecord::RecordNotFound from collection `*_ids` setters
2+
for unknown IDs with a better error message.
3+
4+
Changes the collection `*_ids` setters to cast provided IDs the data
5+
type of the primary key set in the association, not the model
6+
primary key.
7+
8+
*Dominic Cleal*
9+
110
* For PostgreSQL >= 9.4 use `pgcrypto`'s `gen_random_uuid()` instead of
211
`uuid-ossp`'s UUID generation function.
312

activerecord/lib/active_record/associations/collection_association.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,17 @@ def ids_reader
6868

6969
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
7070
def ids_writer(ids)
71-
pk_type = reflection.primary_key_type
71+
pk_type = reflection.association_primary_key_type
7272
ids = Array(ids).reject(&:blank?)
7373
ids.map! { |i| pk_type.cast(i) }
7474
records = klass.where(reflection.association_primary_key => ids).index_by do |r|
7575
r.send(reflection.association_primary_key)
76-
end.values_at(*ids)
77-
replace(records)
76+
end.values_at(*ids).compact
77+
if records.size != ids.size
78+
scope.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key)
79+
else
80+
replace(records)
81+
end
7882
end
7983

8084
def reset

activerecord/lib/active_record/reflection.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ def association_primary_key(klass = nil)
397397
options[:primary_key] || primary_key(klass || self.klass)
398398
end
399399

400+
def association_primary_key_type
401+
klass.type_for_attribute(association_primary_key)
402+
end
403+
400404
def active_record_primary_key
401405
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
402406
end

activerecord/lib/active_record/relation/finder_methods.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def exists?(conditions = :none)
345345
# of results obtained should be provided in the +result_size+ argument and
346346
# the expected number of results should be provided in the +expected_size+
347347
# argument.
348-
def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil) # :nodoc:
348+
def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil, key = primary_key) # :nodoc:
349349
conditions = arel.where_sql(@klass.arel_engine)
350350
conditions = " [#{conditions}]" if conditions
351351
name = @klass.name
@@ -355,10 +355,10 @@ def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_siz
355355
error << " with#{conditions}" if conditions
356356
raise RecordNotFound.new(error, name)
357357
elsif Array(ids).size == 1
358-
error = "Couldn't find #{name} with '#{primary_key}'=#{ids}#{conditions}"
359-
raise RecordNotFound.new(error, name, primary_key, ids)
358+
error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
359+
raise RecordNotFound.new(error, name, key, ids)
360360
else
361-
error = "Couldn't find all #{name.pluralize} with '#{primary_key}': "
361+
error = "Couldn't find all #{name.pluralize} with '#{key}': "
362362
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"
363363

364364
raise RecordNotFound.new(error, name, primary_key, ids)

activerecord/test/cases/associations/has_many_through_associations_test.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,10 +883,25 @@ def test_collection_singular_ids_setter_with_string_primary_keys
883883

884884
end
885885

886+
def test_collection_singular_ids_setter_with_changed_primary_key
887+
company = companies(:first_firm)
888+
client = companies(:first_client)
889+
company.clients_using_primary_key_ids = [client.name]
890+
assert_equal [client], company.clients_using_primary_key
891+
end
892+
886893
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
887894
company = companies(:rails_core)
888895
ids = [Developer.first.id, -9999]
889-
assert_raises(ActiveRecord::AssociationTypeMismatch) { company.developer_ids = ids }
896+
e = assert_raises(ActiveRecord::RecordNotFound) { company.developer_ids = ids }
897+
assert_match(/Couldn't find all Developers with 'id'/, e.message)
898+
end
899+
900+
def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set_with_changed_primary_key
901+
company = companies(:first_firm)
902+
ids = [Client.first.name, "unknown client"]
903+
e = assert_raises(ActiveRecord::RecordNotFound) { company.clients_using_primary_key_ids = ids }
904+
assert_match(/Couldn't find all Clients with 'name'/, e.message)
890905
end
891906

892907
def test_build_a_model_from_hm_through_association_with_where_clause

0 commit comments

Comments
 (0)