/usr/lib/ruby/vendor_ruby/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb is in ruby-shoulda-matchers 1.0.0~beta2-1build1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | module Shoulda # :nodoc:
module Matchers
module ActiveRecord # :nodoc:
# Ensures that the model is invalid if the given attribute is not unique.
#
# Internally, this uses values from existing records to test validations,
# so this will always fail if you have not saved at least one record for
# the model being tested, like so:
#
# describe User do
# before(:each) { User.create!(:email => 'address@example.com') }
# it { should validate_uniqueness_of(:email) }
# end
#
# Options:
#
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. <tt>Regexp</tt> or <tt>String</tt>.
# Defaults to the translation for <tt>:taken</tt>.
# * <tt>scoped_to</tt> - field(s) to scope the uniqueness to.
# * <tt>case_insensitive</tt> - ensures that the validation does not
# check case. Off by default. Ignored by non-text attributes.
#
# Examples:
# it { should validate_uniqueness_of(:keyword) }
# it { should validate_uniqueness_of(:keyword).with_message(/dup/) }
# it { should validate_uniqueness_of(:email).scoped_to(:name) }
# it { should validate_uniqueness_of(:email).
# scoped_to(:first_name, :last_name) }
# it { should validate_uniqueness_of(:keyword).case_insensitive }
#
def validate_uniqueness_of(attr)
ValidateUniquenessOfMatcher.new(attr)
end
class ValidateUniquenessOfMatcher < ValidationMatcher # :nodoc:
include Helpers
def initialize(attribute)
@attribute = attribute
end
def scoped_to(*scopes)
@scopes = [*scopes].flatten
self
end
def with_message(message)
@expected_message = message
self
end
def case_insensitive
@case_insensitive = true
self
end
def description
result = "require "
result << "case sensitive " unless @case_insensitive
result << "unique value for #{@attribute}"
result << " scoped to #{@scopes.join(', ')}" unless @scopes.blank?
result
end
def matches?(subject)
@subject = subject.class.new
@expected_message ||= :taken
find_existing &&
set_scoped_attributes &&
validate_attribute &&
validate_after_scope_change
end
private
def find_existing
if @existing = @subject.class.find(:first)
true
else
@failure_message = "Can't find first #{class_name}"
false
end
end
def set_scoped_attributes
unless @scopes.blank?
@scopes.each do |scope|
setter = :"#{scope}="
unless @subject.respond_to?(setter)
@failure_message =
"#{class_name} doesn't seem to have a #{scope} attribute."
return false
end
@subject.send("#{scope}=", @existing.send(scope))
end
end
true
end
def validate_attribute
disallows_value_of(existing_value, @expected_message)
end
# TODO: There is a chance that we could change the scoped field
# to a value that's already taken. An alternative implementation
# could actually find all values for scope and create a unique
def validate_after_scope_change
if @scopes.blank?
true
else
@scopes.all? do |scope|
previous_value = @existing.send(scope)
# Assume the scope is a foreign key if the field is nil
previous_value ||= 0
next_value = previous_value.next
@subject.send("#{scope}=", next_value)
if allows_value_of(existing_value, @expected_message)
@negative_failure_message <<
" (with different value of #{scope})"
true
else
@failure_message << " (with different value of #{scope})"
false
end
end
end
end
def class_name
@subject.class.name
end
def existing_value
value = @existing.send(@attribute)
value.swapcase! if @case_insensitive && value.respond_to?(:swapcase!)
value
end
end
end
end
end
|