thoughtbot/shoulda-matchers — pull request #587 by akasper (2014-09-16 20:20:03+00:00)
Title: Fix invalid UUID being generated in #should_validate_uniqueness_of

When performing a scoped match against a scoped association whose primary key is a UUID using the `ValidateUniquenessOfMatcher`, it is possible to generate an invalid UUID. Specifically, if the final character of the UUID is 'f', the call to `#validate_after_scope_change?` fails. This causes specs to fail nondeterministically.

The subtle offending error is in this region of code from `lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb` in the `#validate_after_scope_change?` method:

```ruby
# Assume the scope is a foreign key if the field is nil
previous_value ||= correct_type_for_column(@subject.class.columns_hash[scope.to_s])

next_value =
if @subject.class.respond_to?(:defined_enums) && @subject.defined_enums[scope.to_s]
available_values = @subject.defined_enums[scope.to_s].reject do |key, _|
key == previous_value
end
available_values.keys.last
elsif previous_value.respond_to?(:next)
previous_value.next
elsif previous_value.respond_to?(:to_datetime)
previous_value.to_datetime.next
else
previous_value.to_s.next
end
```
Consider the case where the value `previous_value` generated via the call to `#correct_type_for_column` generates the UUID `20dc0489-5496-4a4e-ab0d-5a0cd9de668f`. In this case, the `next_value` will fall through to `elsif previous_value.respond_to?(:next)`, because UUIDs will be persisted in most ORMs as `String`s. Calling `#next` on this string yields the value `20dc0489-5496-4a4e-ab0d-5a0cd9de668g`, an invalid UUID.

The change is to catch those values where the underlying scoped field is a UUID and simply supply a new `SecureRandom.uuid`. This approximately matches the "incrementing" behavior of nearly every database system which uses UUIDs anyway, causes no apparent issues with this project's spec library, and is unlikely to cause errors in the wild.

This PR provides no specs, as the API modified is private, and it is the custom of this project not to provide spec coverage for private APIs. (I will gladly accept suggestions of a good spec to provide should the maintainers require it.)