remarkable_activerecord 3.1.8 → 3.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +140 -138
- data/LICENSE +20 -20
- data/README +80 -80
- data/lib/remarkable_activerecord.rb +29 -29
- data/lib/remarkable_activerecord/base.rb +248 -237
- data/lib/remarkable_activerecord/describe.rb +27 -27
- data/lib/remarkable_activerecord/human_names.rb +36 -36
- data/lib/remarkable_activerecord/matchers/accept_nested_attributes_for_matcher.rb +30 -30
- data/lib/remarkable_activerecord/matchers/allow_mass_assignment_of_matcher.rb +59 -59
- data/lib/remarkable_activerecord/matchers/allow_values_for_matcher.rb +85 -94
- data/lib/remarkable_activerecord/matchers/association_matcher.rb +283 -283
- data/lib/remarkable_activerecord/matchers/have_column_matcher.rb +68 -68
- data/lib/remarkable_activerecord/matchers/have_default_scope_matcher.rb +38 -38
- data/lib/remarkable_activerecord/matchers/have_index_matcher.rb +73 -73
- data/lib/remarkable_activerecord/matchers/have_readonly_attributes_matcher.rb +30 -30
- data/lib/remarkable_activerecord/matchers/have_scope_matcher.rb +85 -85
- data/lib/remarkable_activerecord/matchers/validate_acceptance_of_matcher.rb +50 -50
- data/lib/remarkable_activerecord/matchers/validate_associated_matcher.rb +97 -97
- data/lib/remarkable_activerecord/matchers/validate_confirmation_of_matcher.rb +44 -44
- data/lib/remarkable_activerecord/matchers/validate_exclusion_of_matcher.rb +53 -53
- data/lib/remarkable_activerecord/matchers/validate_inclusion_of_matcher.rb +52 -52
- data/lib/remarkable_activerecord/matchers/validate_length_of_matcher.rb +150 -150
- data/lib/remarkable_activerecord/matchers/validate_numericality_of_matcher.rb +181 -181
- data/lib/remarkable_activerecord/matchers/validate_presence_of_matcher.rb +29 -29
- data/lib/remarkable_activerecord/matchers/validate_uniqueness_of_matcher.rb +233 -233
- data/locale/en.yml +261 -261
- data/spec/accept_nested_attributes_for_matcher_spec.rb +1 -1
- data/spec/allow_mass_assignment_of_matcher_spec.rb +90 -82
- data/spec/allow_values_for_matcher_spec.rb +72 -63
- data/spec/association_matcher_spec.rb +612 -612
- data/spec/describe_spec.rb +3 -3
- data/spec/have_column_matcher_spec.rb +73 -73
- data/spec/have_default_scope_matcher_spec.rb +1 -1
- data/spec/have_index_matcher_spec.rb +87 -87
- data/spec/have_readonly_attributes_matcher_spec.rb +47 -47
- data/spec/have_scope_matcher_spec.rb +77 -77
- data/spec/model_builder.rb +101 -101
- data/spec/rcov.opts +1 -1
- data/spec/spec.opts +4 -4
- data/spec/spec_helper.rb +27 -27
- data/spec/validate_acceptance_of_matcher_spec.rb +68 -68
- data/spec/validate_associated_matcher_spec.rb +121 -121
- data/spec/validate_confirmation_of_matcher_spec.rb +58 -58
- data/spec/validate_length_of_matcher_spec.rb +218 -218
- data/spec/validate_numericality_of_matcher_spec.rb +179 -179
- data/spec/validate_presence_of_matcher_spec.rb +56 -56
- data/spec/validate_uniqueness_of_matcher_spec.rb +164 -164
- metadata +5 -5
@@ -1,4 +1,4 @@
|
|
1
|
-
module Remarkable
|
1
|
+
module Remarkable
|
2
2
|
module ActiveRecord
|
3
3
|
# Holds ActiveRecord matchers.
|
4
4
|
#
|
@@ -34,13 +34,13 @@ module Remarkable
|
|
34
34
|
#
|
35
35
|
# However, if you change the title using the I18n API, you don't need to
|
36
36
|
# specify the message in your tests, because it's retrieved properly.
|
37
|
-
#
|
38
|
-
module Matchers
|
39
|
-
class ValidatePresenceOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
|
40
|
-
arguments :collection => :attributes, :as => :attribute
|
41
|
-
optional :message
|
42
|
-
|
43
|
-
collection_assertions :allow_nil?
|
37
|
+
#
|
38
|
+
module Matchers
|
39
|
+
class ValidatePresenceOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
|
40
|
+
arguments :collection => :attributes, :as => :attribute
|
41
|
+
optional :message
|
42
|
+
|
43
|
+
collection_assertions :allow_nil?
|
44
44
|
default_options :message => :blank
|
45
45
|
|
46
46
|
protected
|
@@ -60,24 +60,24 @@ module Remarkable
|
|
60
60
|
false
|
61
61
|
end
|
62
62
|
end
|
63
|
-
|
64
|
-
end
|
65
|
-
|
66
|
-
# Ensures that the model cannot be saved if one of the attributes listed is not present.
|
67
|
-
#
|
68
|
-
# == Options
|
69
|
-
#
|
70
|
-
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
71
|
-
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
|
72
|
-
#
|
73
|
-
# == Examples
|
74
|
-
#
|
75
|
-
# should_validate_presence_of :name, :phone_number
|
76
|
-
# it { should validate_presence_of(:name, :phone_number) }
|
77
|
-
#
|
78
|
-
def validate_presence_of(*args, &block)
|
79
|
-
ValidatePresenceOfMatcher.new(*args, &block).spec(self)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not present.
|
67
|
+
#
|
68
|
+
# == Options
|
69
|
+
#
|
70
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
71
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.blank')</tt>
|
72
|
+
#
|
73
|
+
# == Examples
|
74
|
+
#
|
75
|
+
# should_validate_presence_of :name, :phone_number
|
76
|
+
# it { should validate_presence_of(:name, :phone_number) }
|
77
|
+
#
|
78
|
+
def validate_presence_of(*args, &block)
|
79
|
+
ValidatePresenceOfMatcher.new(*args, &block).spec(self)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -1,233 +1,233 @@
|
|
1
|
-
module Remarkable
|
2
|
-
module ActiveRecord
|
3
|
-
module Matchers
|
4
|
-
class ValidateUniquenessOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
|
5
|
-
arguments :collection => :attributes, :as => :attribute
|
6
|
-
|
7
|
-
optional :message
|
8
|
-
optional :scope, :splat => true
|
9
|
-
optional :case_sensitive, :allow_nil, :allow_blank, :default => true
|
10
|
-
|
11
|
-
collection_assertions :find_first_object?, :responds_to_scope?, :is_unique?, :case_sensitive?,
|
12
|
-
:valid_with_new_scope?, :allow_nil?, :allow_blank?
|
13
|
-
|
14
|
-
default_options :message => :taken
|
15
|
-
|
16
|
-
before_assert do
|
17
|
-
@options[:scope] = [*@options[:scope]].compact if @options[:scope]
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
# Tries to find an object in the database. If allow_nil and/or allow_blank
|
23
|
-
# is given, we must find a record which is not nil or not blank.
|
24
|
-
#
|
25
|
-
# We should also ensure that the object retrieved from the database
|
26
|
-
# is not the @subject.
|
27
|
-
#
|
28
|
-
# If any of these attempts fail, an error is raised.
|
29
|
-
#
|
30
|
-
def find_first_object?
|
31
|
-
conditions, message = if @options[:allow_nil]
|
32
|
-
[ ["#{@attribute} IS NOT NULL"], " with #{@attribute} not nil" ]
|
33
|
-
elsif @options[:allow_blank]
|
34
|
-
[ ["#{@attribute} != ''"], " with #{@attribute} not blank" ]
|
35
|
-
else
|
36
|
-
[ [], "" ]
|
37
|
-
end
|
38
|
-
|
39
|
-
unless @subject.new_record?
|
40
|
-
primary_key = subject_class.primary_key
|
41
|
-
|
42
|
-
message << " which is different from the subject record (the object being validated is the same as the one in the database)"
|
43
|
-
conditions << "#{subject_class.primary_key} != '#{@subject.send(primary_key)}'"
|
44
|
-
end
|
45
|
-
|
46
|
-
options = conditions.empty? ? {} : { :conditions => conditions.join(' AND ') }
|
47
|
-
|
48
|
-
return true if @existing = subject_class.find(:first, options)
|
49
|
-
raise ScriptError, "could not find a #{subject_class} record in the database" + message
|
50
|
-
end
|
51
|
-
|
52
|
-
# Set subject scope to be equal to the object found.
|
53
|
-
#
|
54
|
-
def responds_to_scope?
|
55
|
-
(@options[:scope] || []).each do |scope|
|
56
|
-
setter = :"#{scope}="
|
57
|
-
|
58
|
-
return false, :method => setter unless @subject.respond_to?(setter)
|
59
|
-
return false, :method => scope unless @existing.respond_to?(scope)
|
60
|
-
|
61
|
-
@subject.send(setter, @existing.send(scope))
|
62
|
-
end
|
63
|
-
true
|
64
|
-
end
|
65
|
-
|
66
|
-
# Check if the attribute given is valid and if the validation fails for equal values.
|
67
|
-
#
|
68
|
-
def is_unique?
|
69
|
-
@value = @existing.send(@attribute)
|
70
|
-
return bad?(@value)
|
71
|
-
end
|
72
|
-
|
73
|
-
# If :case_sensitive is given and it's false, we swap the case of the
|
74
|
-
# value used in :is_unique? and see if the test object remains valid.
|
75
|
-
#
|
76
|
-
# If :case_sensitive is given and it's true, we swap the case of the
|
77
|
-
# value used in is_unique? and see if the test object is not valid.
|
78
|
-
#
|
79
|
-
# This validation will only occur if the test object is a String.
|
80
|
-
#
|
81
|
-
def case_sensitive?
|
82
|
-
return true unless @value.is_a?(String)
|
83
|
-
assert_good_or_bad_if_key(:case_sensitive, @value.swapcase)
|
84
|
-
end
|
85
|
-
|
86
|
-
# Now test that the object is valid when changing the scoped attribute.
|
87
|
-
#
|
88
|
-
def valid_with_new_scope?
|
89
|
-
(@options[:scope] || []).each do |scope|
|
90
|
-
setter = :"#{scope}="
|
91
|
-
|
92
|
-
previous_scope_value = @subject.send(scope)
|
93
|
-
@subject.send(setter, new_value_for_scope(scope))
|
94
|
-
return false, :method => scope unless good?(@value)
|
95
|
-
|
96
|
-
@subject.send(setter, previous_scope_value)
|
97
|
-
end
|
98
|
-
true
|
99
|
-
end
|
100
|
-
|
101
|
-
# Change the existing object attribute to nil to run allow nil
|
102
|
-
# validations. If we find any problem while updating the @existing
|
103
|
-
# record, it's because we can't save nil values in the database. So it
|
104
|
-
# passes when :allow_nil is false, but should raise an error when
|
105
|
-
# :allow_nil is true
|
106
|
-
#
|
107
|
-
def allow_nil?
|
108
|
-
return true unless @options.key?(:allow_nil)
|
109
|
-
|
110
|
-
begin
|
111
|
-
@existing.update_attribute(@attribute, nil)
|
112
|
-
rescue ::ActiveRecord::StatementInvalid => e
|
113
|
-
raise ScriptError, "You declared that #{@attribute} accepts nil values in validate_uniqueness_of, " <<
|
114
|
-
"but I cannot save nil values in the database, got: #{e.message}" if @options[:allow_nil]
|
115
|
-
return true
|
116
|
-
end
|
117
|
-
|
118
|
-
super
|
119
|
-
end
|
120
|
-
|
121
|
-
# Change the existing object attribute to blank to run allow blank
|
122
|
-
# validation. It uses the same logic as :allow_nil.
|
123
|
-
#
|
124
|
-
def allow_blank?
|
125
|
-
return true unless @options.key?(:allow_blank)
|
126
|
-
|
127
|
-
begin
|
128
|
-
@existing.update_attribute(@attribute, '')
|
129
|
-
rescue ::ActiveRecord::StatementInvalid => e
|
130
|
-
raise ScriptError, "You declared that #{@attribute} accepts blank values in validate_uniqueness_of, " <<
|
131
|
-
"but I cannot save blank values in the database, got: #{e.message}" if @options[:allow_blank]
|
132
|
-
return true
|
133
|
-
end
|
134
|
-
|
135
|
-
super
|
136
|
-
end
|
137
|
-
|
138
|
-
# Returns a value to be used as new scope. It deals with four different
|
139
|
-
# cases: date, time, boolean and stringfiable (everything that can be
|
140
|
-
# converted to a string and the next value makes sense)
|
141
|
-
#
|
142
|
-
def new_value_for_scope(scope)
|
143
|
-
column_type = if @existing.respond_to?(:column_for_attribute)
|
144
|
-
@existing.column_for_attribute(scope)
|
145
|
-
else
|
146
|
-
nil
|
147
|
-
end
|
148
|
-
|
149
|
-
case column_type.type
|
150
|
-
when :int, :integer, :float, :decimal
|
151
|
-
new_value_for_stringfiable_scope(scope)
|
152
|
-
when :datetime, :timestamp, :time
|
153
|
-
Time.now + 10000
|
154
|
-
when :date
|
155
|
-
Date.today + 100
|
156
|
-
when :boolean
|
157
|
-
!@existing.send(scope)
|
158
|
-
else
|
159
|
-
new_value_for_stringfiable_scope(scope)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# Returns a value to be used as scope by generating a range of values
|
164
|
-
# and searching for them in the database.
|
165
|
-
#
|
166
|
-
def new_value_for_stringfiable_scope(scope)
|
167
|
-
values = [(@existing.send(scope) || 999).next.to_s]
|
168
|
-
|
169
|
-
# Generate a range of values to search in the database
|
170
|
-
100.times do
|
171
|
-
values << values.last.next
|
172
|
-
end
|
173
|
-
conditions = { scope => values, @attribute => @value }
|
174
|
-
|
175
|
-
# Get values from the database, get the scope attribute and map them to string.
|
176
|
-
db_values = subject_class.find(:all, :conditions => conditions, :select => scope)
|
177
|
-
db_values.map!{ |r| r.send(scope).to_s }
|
178
|
-
|
179
|
-
if value_to_return = (values - db_values).first
|
180
|
-
value_to_return
|
181
|
-
else
|
182
|
-
raise ScriptError, "Tried to find an unique scope value for #{scope} but I could not. " <<
|
183
|
-
"The conditions hash was #{conditions.inspect} and it returned all records."
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
# Ensures that the model cannot be saved if one of the attributes listed
|
189
|
-
# is not unique.
|
190
|
-
#
|
191
|
-
# Requires an existing record in the database. If you supply :allow_nil as
|
192
|
-
# option, you need to have in the database a record which is not nil in the
|
193
|
-
# given attributes. The same is required for allow_blank option.
|
194
|
-
#
|
195
|
-
# Notice that the record being validate should not be the same as in the
|
196
|
-
# database. In other words, you can't do this:
|
197
|
-
#
|
198
|
-
# subject { Post.create!(@valid_attributes) }
|
199
|
-
# should_validate_uniqueness_of :title
|
200
|
-
#
|
201
|
-
# But don't worry, if you eventually do that, a helpful error message
|
202
|
-
# will be raised.
|
203
|
-
#
|
204
|
-
# == Options
|
205
|
-
#
|
206
|
-
# * <tt>:scope</tt> - field(s) to scope the uniqueness to.
|
207
|
-
# * <tt>:case_sensitive</tt> - the matcher look for an exact match.
|
208
|
-
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
|
209
|
-
# * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
|
210
|
-
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
211
|
-
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
|
212
|
-
#
|
213
|
-
# == Examples
|
214
|
-
#
|
215
|
-
# it { should validate_uniqueness_of(:keyword, :username) }
|
216
|
-
# it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
|
217
|
-
# it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
|
218
|
-
#
|
219
|
-
# should_validate_uniqueness_of :keyword, :username
|
220
|
-
# should_validate_uniqueness_of :email, :scope => :name, :case_sensitive => false
|
221
|
-
# should_validate_uniqueness_of :address, :scope => [:first_name, :last_name]
|
222
|
-
#
|
223
|
-
# should_validate_uniqueness_of :email do |m|
|
224
|
-
# m.scope = name
|
225
|
-
# m.case_sensitive = false
|
226
|
-
# end
|
227
|
-
#
|
228
|
-
def validate_uniqueness_of(*attributes, &block)
|
229
|
-
ValidateUniquenessOfMatcher.new(*attributes, &block).spec(self)
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
1
|
+
module Remarkable
|
2
|
+
module ActiveRecord
|
3
|
+
module Matchers
|
4
|
+
class ValidateUniquenessOfMatcher < Remarkable::ActiveRecord::Base #:nodoc:
|
5
|
+
arguments :collection => :attributes, :as => :attribute
|
6
|
+
|
7
|
+
optional :message
|
8
|
+
optional :scope, :splat => true
|
9
|
+
optional :case_sensitive, :allow_nil, :allow_blank, :default => true
|
10
|
+
|
11
|
+
collection_assertions :find_first_object?, :responds_to_scope?, :is_unique?, :case_sensitive?,
|
12
|
+
:valid_with_new_scope?, :allow_nil?, :allow_blank?
|
13
|
+
|
14
|
+
default_options :message => :taken
|
15
|
+
|
16
|
+
before_assert do
|
17
|
+
@options[:scope] = [*@options[:scope]].compact if @options[:scope]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Tries to find an object in the database. If allow_nil and/or allow_blank
|
23
|
+
# is given, we must find a record which is not nil or not blank.
|
24
|
+
#
|
25
|
+
# We should also ensure that the object retrieved from the database
|
26
|
+
# is not the @subject.
|
27
|
+
#
|
28
|
+
# If any of these attempts fail, an error is raised.
|
29
|
+
#
|
30
|
+
def find_first_object?
|
31
|
+
conditions, message = if @options[:allow_nil]
|
32
|
+
[ ["#{@attribute} IS NOT NULL"], " with #{@attribute} not nil" ]
|
33
|
+
elsif @options[:allow_blank]
|
34
|
+
[ ["#{@attribute} != ''"], " with #{@attribute} not blank" ]
|
35
|
+
else
|
36
|
+
[ [], "" ]
|
37
|
+
end
|
38
|
+
|
39
|
+
unless @subject.new_record?
|
40
|
+
primary_key = subject_class.primary_key
|
41
|
+
|
42
|
+
message << " which is different from the subject record (the object being validated is the same as the one in the database)"
|
43
|
+
conditions << "#{subject_class.primary_key} != '#{@subject.send(primary_key)}'"
|
44
|
+
end
|
45
|
+
|
46
|
+
options = conditions.empty? ? {} : { :conditions => conditions.join(' AND ') }
|
47
|
+
|
48
|
+
return true if @existing = subject_class.find(:first, options)
|
49
|
+
raise ScriptError, "could not find a #{subject_class} record in the database" + message
|
50
|
+
end
|
51
|
+
|
52
|
+
# Set subject scope to be equal to the object found.
|
53
|
+
#
|
54
|
+
def responds_to_scope?
|
55
|
+
(@options[:scope] || []).each do |scope|
|
56
|
+
setter = :"#{scope}="
|
57
|
+
|
58
|
+
return false, :method => setter unless @subject.respond_to?(setter)
|
59
|
+
return false, :method => scope unless @existing.respond_to?(scope)
|
60
|
+
|
61
|
+
@subject.send(setter, @existing.send(scope))
|
62
|
+
end
|
63
|
+
true
|
64
|
+
end
|
65
|
+
|
66
|
+
# Check if the attribute given is valid and if the validation fails for equal values.
|
67
|
+
#
|
68
|
+
def is_unique?
|
69
|
+
@value = @existing.send(@attribute)
|
70
|
+
return bad?(@value)
|
71
|
+
end
|
72
|
+
|
73
|
+
# If :case_sensitive is given and it's false, we swap the case of the
|
74
|
+
# value used in :is_unique? and see if the test object remains valid.
|
75
|
+
#
|
76
|
+
# If :case_sensitive is given and it's true, we swap the case of the
|
77
|
+
# value used in is_unique? and see if the test object is not valid.
|
78
|
+
#
|
79
|
+
# This validation will only occur if the test object is a String.
|
80
|
+
#
|
81
|
+
def case_sensitive?
|
82
|
+
return true unless @value.is_a?(String)
|
83
|
+
assert_good_or_bad_if_key(:case_sensitive, @value.swapcase)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Now test that the object is valid when changing the scoped attribute.
|
87
|
+
#
|
88
|
+
def valid_with_new_scope?
|
89
|
+
(@options[:scope] || []).each do |scope|
|
90
|
+
setter = :"#{scope}="
|
91
|
+
|
92
|
+
previous_scope_value = @subject.send(scope)
|
93
|
+
@subject.send(setter, new_value_for_scope(scope))
|
94
|
+
return false, :method => scope unless good?(@value)
|
95
|
+
|
96
|
+
@subject.send(setter, previous_scope_value)
|
97
|
+
end
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
# Change the existing object attribute to nil to run allow nil
|
102
|
+
# validations. If we find any problem while updating the @existing
|
103
|
+
# record, it's because we can't save nil values in the database. So it
|
104
|
+
# passes when :allow_nil is false, but should raise an error when
|
105
|
+
# :allow_nil is true
|
106
|
+
#
|
107
|
+
def allow_nil?
|
108
|
+
return true unless @options.key?(:allow_nil)
|
109
|
+
|
110
|
+
begin
|
111
|
+
@existing.update_attribute(@attribute, nil)
|
112
|
+
rescue ::ActiveRecord::StatementInvalid => e
|
113
|
+
raise ScriptError, "You declared that #{@attribute} accepts nil values in validate_uniqueness_of, " <<
|
114
|
+
"but I cannot save nil values in the database, got: #{e.message}" if @options[:allow_nil]
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
|
118
|
+
super
|
119
|
+
end
|
120
|
+
|
121
|
+
# Change the existing object attribute to blank to run allow blank
|
122
|
+
# validation. It uses the same logic as :allow_nil.
|
123
|
+
#
|
124
|
+
def allow_blank?
|
125
|
+
return true unless @options.key?(:allow_blank)
|
126
|
+
|
127
|
+
begin
|
128
|
+
@existing.update_attribute(@attribute, '')
|
129
|
+
rescue ::ActiveRecord::StatementInvalid => e
|
130
|
+
raise ScriptError, "You declared that #{@attribute} accepts blank values in validate_uniqueness_of, " <<
|
131
|
+
"but I cannot save blank values in the database, got: #{e.message}" if @options[:allow_blank]
|
132
|
+
return true
|
133
|
+
end
|
134
|
+
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns a value to be used as new scope. It deals with four different
|
139
|
+
# cases: date, time, boolean and stringfiable (everything that can be
|
140
|
+
# converted to a string and the next value makes sense)
|
141
|
+
#
|
142
|
+
def new_value_for_scope(scope)
|
143
|
+
column_type = if @existing.respond_to?(:column_for_attribute)
|
144
|
+
@existing.column_for_attribute(scope)
|
145
|
+
else
|
146
|
+
nil
|
147
|
+
end
|
148
|
+
|
149
|
+
case column_type.type
|
150
|
+
when :int, :integer, :float, :decimal
|
151
|
+
new_value_for_stringfiable_scope(scope)
|
152
|
+
when :datetime, :timestamp, :time
|
153
|
+
Time.now + 10000
|
154
|
+
when :date
|
155
|
+
Date.today + 100
|
156
|
+
when :boolean
|
157
|
+
!@existing.send(scope)
|
158
|
+
else
|
159
|
+
new_value_for_stringfiable_scope(scope)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns a value to be used as scope by generating a range of values
|
164
|
+
# and searching for them in the database.
|
165
|
+
#
|
166
|
+
def new_value_for_stringfiable_scope(scope)
|
167
|
+
values = [(@existing.send(scope) || 999).next.to_s]
|
168
|
+
|
169
|
+
# Generate a range of values to search in the database
|
170
|
+
100.times do
|
171
|
+
values << values.last.next
|
172
|
+
end
|
173
|
+
conditions = { scope => values, @attribute => @value }
|
174
|
+
|
175
|
+
# Get values from the database, get the scope attribute and map them to string.
|
176
|
+
db_values = subject_class.find(:all, :conditions => conditions, :select => scope)
|
177
|
+
db_values.map!{ |r| r.send(scope).to_s }
|
178
|
+
|
179
|
+
if value_to_return = (values - db_values).first
|
180
|
+
value_to_return
|
181
|
+
else
|
182
|
+
raise ScriptError, "Tried to find an unique scope value for #{scope} but I could not. " <<
|
183
|
+
"The conditions hash was #{conditions.inspect} and it returned all records."
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Ensures that the model cannot be saved if one of the attributes listed
|
189
|
+
# is not unique.
|
190
|
+
#
|
191
|
+
# Requires an existing record in the database. If you supply :allow_nil as
|
192
|
+
# option, you need to have in the database a record which is not nil in the
|
193
|
+
# given attributes. The same is required for allow_blank option.
|
194
|
+
#
|
195
|
+
# Notice that the record being validate should not be the same as in the
|
196
|
+
# database. In other words, you can't do this:
|
197
|
+
#
|
198
|
+
# subject { Post.create!(@valid_attributes) }
|
199
|
+
# should_validate_uniqueness_of :title
|
200
|
+
#
|
201
|
+
# But don't worry, if you eventually do that, a helpful error message
|
202
|
+
# will be raised.
|
203
|
+
#
|
204
|
+
# == Options
|
205
|
+
#
|
206
|
+
# * <tt>:scope</tt> - field(s) to scope the uniqueness to.
|
207
|
+
# * <tt>:case_sensitive</tt> - the matcher look for an exact match.
|
208
|
+
# * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
|
209
|
+
# * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
|
210
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
211
|
+
# Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.taken')</tt>
|
212
|
+
#
|
213
|
+
# == Examples
|
214
|
+
#
|
215
|
+
# it { should validate_uniqueness_of(:keyword, :username) }
|
216
|
+
# it { should validate_uniqueness_of(:email, :scope => :name, :case_sensitive => false) }
|
217
|
+
# it { should validate_uniqueness_of(:address, :scope => [:first_name, :last_name]) }
|
218
|
+
#
|
219
|
+
# should_validate_uniqueness_of :keyword, :username
|
220
|
+
# should_validate_uniqueness_of :email, :scope => :name, :case_sensitive => false
|
221
|
+
# should_validate_uniqueness_of :address, :scope => [:first_name, :last_name]
|
222
|
+
#
|
223
|
+
# should_validate_uniqueness_of :email do |m|
|
224
|
+
# m.scope = name
|
225
|
+
# m.case_sensitive = false
|
226
|
+
# end
|
227
|
+
#
|
228
|
+
def validate_uniqueness_of(*attributes, &block)
|
229
|
+
ValidateUniquenessOfMatcher.new(*attributes, &block).spec(self)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|