Skip to content

Commit

Permalink
Merge pull request #26718 from domcleal/5-0-stable-ids-writer-exception
Browse files Browse the repository at this point in the history
Restore RecordNotFound when *_ids= can't find records by ID
  • Loading branch information
matthewd committed Nov 24, 2016
1 parent b3fd17a commit ba66ed0
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 9 deletions.
9 changes: 9 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,12 @@
* Raise ActiveRecord::RecordNotFound from collection `*_ids` setters
for unknown IDs with a better error message.

Changes the collection `*_ids` setters to cast provided IDs the data
type of the primary key set in the association, not the model
primary key.

*Dominic Cleal*

* Introduce `Model#reload_<association>` to bring back the behavior
of `Article.category(true)` where `category` is a singular
association.
Expand Down
Expand Up @@ -69,13 +69,17 @@ def ids_reader

# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
def ids_writer(ids)
pk_type = reflection.primary_key_type
pk_type = reflection.association_primary_key_type
ids = Array(ids).reject(&:blank?)
ids.map! { |i| pk_type.cast(i) }
records = klass.where(reflection.association_primary_key => ids).index_by do |r|
r.send(reflection.association_primary_key)
end.values_at(*ids)
replace(records)
end.values_at(*ids).compact
if records.size != ids.size
scope.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key)
else
replace(records)
end
end

def reset
Expand Down
4 changes: 4 additions & 0 deletions activerecord/lib/active_record/reflection.rb
Expand Up @@ -398,6 +398,10 @@ def association_primary_key(klass = nil)
options[:primary_key] || primary_key(klass || self.klass)
end

def association_primary_key_type
klass.type_for_attribute(association_primary_key)
end

def active_record_primary_key
@active_record_primary_key ||= options[:primary_key] || primary_key(active_record)
end
Expand Down
8 changes: 4 additions & 4 deletions activerecord/lib/active_record/relation/finder_methods.rb
Expand Up @@ -343,16 +343,16 @@ def exists?(conditions = :none)
# of results obtained should be provided in the +result_size+ argument and
# the expected number of results should be provided in the +expected_size+
# argument.
def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
def raise_record_not_found_exception!(ids, result_size, expected_size, key = primary_key) #:nodoc:
conditions = arel.where_sql(@klass.arel_engine)
conditions = " [#{conditions}]" if conditions
name = @klass.name

if Array(ids).size == 1
error = "Couldn't find #{name} with '#{primary_key}'=#{ids}#{conditions}"
raise RecordNotFound.new(error, name, primary_key, ids)
error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}"
raise RecordNotFound.new(error, name, key, ids)
else
error = "Couldn't find all #{name.pluralize} with '#{primary_key}': "
error = "Couldn't find all #{name.pluralize} with '#{key}': "
error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})"

raise RecordNotFound, error
Expand Down
Expand Up @@ -883,10 +883,25 @@ def test_collection_singular_ids_setter_with_string_primary_keys

end

def test_collection_singular_ids_setter_with_changed_primary_key
company = companies(:first_firm)
client = companies(:first_client)
company.clients_using_primary_key_ids = [client.name]
assert_equal [client], company.clients_using_primary_key
end

def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
company = companies(:rails_core)
ids = [Developer.first.id, -9999]
assert_raises(ActiveRecord::AssociationTypeMismatch) {company.developer_ids= ids}
ids = [Developer.first.id, -9999]
e = assert_raises(ActiveRecord::RecordNotFound) { company.developer_ids = ids }
assert_match(/Couldn't find all Developers with 'id'/, e.message)
end

def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set_with_changed_primary_key
company = companies(:first_firm)
ids = [Client.first.name, "unknown client"]
e = assert_raises(ActiveRecord::RecordNotFound) { company.clients_using_primary_key_ids = ids }
assert_match(/Couldn't find all Clients with 'name'/, e.message)
end

def test_build_a_model_from_hm_through_association_with_where_clause
Expand Down

0 comments on commit ba66ed0

Please sign in to comment.