shoulda-activemodel 0.0.2
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/lib/shoulda/active_model.rb +16 -0
- data/lib/shoulda/active_model/assertions.rb +61 -0
- data/lib/shoulda/active_model/helpers.rb +31 -0
- data/lib/shoulda/active_model/macros.rb +268 -0
- data/lib/shoulda/active_model/matchers.rb +30 -0
- data/lib/shoulda/active_model/matchers/allow_value_matcher.rb +102 -0
- data/lib/shoulda/active_model/matchers/ensure_inclusion_of_matcher.rb +87 -0
- data/lib/shoulda/active_model/matchers/ensure_length_of_matcher.rb +141 -0
- data/lib/shoulda/active_model/matchers/validate_acceptance_of_matcher.rb +41 -0
- data/lib/shoulda/active_model/matchers/validate_format_of_matcher.rb +67 -0
- data/lib/shoulda/active_model/matchers/validate_numericality_of_matcher.rb +39 -0
- data/lib/shoulda/active_model/matchers/validate_presence_of_matcher.rb +60 -0
- data/lib/shoulda/active_model/matchers/validate_uniqueness_of_matcher.rb +148 -0
- data/lib/shoulda/active_model/matchers/validation_matcher.rb +57 -0
- data/test/fail_macros.rb +39 -0
- data/test/matchers/active_model/ensure_inclusion_of_matcher_test.rb +80 -0
- data/test/matchers/active_model/ensure_length_of_matcher_test.rb +158 -0
- data/test/matchers/active_model/validate_acceptance_of_matcher_test.rb +44 -0
- data/test/matchers/active_model/validate_format_of_matcher_test.rb +39 -0
- data/test/matchers/active_model/validate_numericality_of_matcher_test.rb +52 -0
- data/test/matchers/active_model/validate_presence_of_matcher_test.rb +86 -0
- data/test/matchers/active_model/validate_uniqueness_of_matcher_test.rb +147 -0
- data/test/model_builder.rb +106 -0
- data/test/rspec_test.rb +207 -0
- data/test/test_helper.rb +20 -0
- metadata +106 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'shoulda'
|
2
|
+
require 'shoulda/active_model/helpers'
|
3
|
+
require 'shoulda/active_model/matchers'
|
4
|
+
require 'shoulda/active_model/assertions'
|
5
|
+
require 'shoulda/active_model/macros'
|
6
|
+
|
7
|
+
module Test # :nodoc: all
|
8
|
+
module Unit
|
9
|
+
class TestCase
|
10
|
+
include Shoulda::ActiveModel::Helpers
|
11
|
+
include Shoulda::ActiveModel::Matchers
|
12
|
+
include Shoulda::ActiveModel::Assertions
|
13
|
+
extend Shoulda::ActiveModel::Macros
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveModel # :nodoc:
|
3
|
+
module Assertions
|
4
|
+
# Asserts that the given object is valid
|
5
|
+
#
|
6
|
+
# assert_valid User.new(params)
|
7
|
+
def assert_valid(obj)
|
8
|
+
assert obj.valid?, "Errors: #{pretty_error_messages obj}"
|
9
|
+
end
|
10
|
+
|
11
|
+
# Asserts that an Active Model model validates with the passed
|
12
|
+
# <tt>value</tt> by making sure the <tt>error_message_to_avoid</tt> is not
|
13
|
+
# contained within the list of errors for that attribute.
|
14
|
+
#
|
15
|
+
# assert_good_value(User.new, :email, "user@example.com")
|
16
|
+
# assert_good_value(User.new, :ssn, "123456789", /length/)
|
17
|
+
#
|
18
|
+
# If a class is passed as the first argument, a new object will be
|
19
|
+
# instantiated before the assertion. If an instance variable exists with
|
20
|
+
# the same name as the class (underscored), that object will be used
|
21
|
+
# instead.
|
22
|
+
#
|
23
|
+
# assert_good_value(User, :email, "user@example.com")
|
24
|
+
#
|
25
|
+
# product = Product.new(:tangible => false)
|
26
|
+
# assert_good_value(product, :price, "0")
|
27
|
+
def assert_good_value(object_or_klass, attribute, value, error_message_to_avoid = nil)
|
28
|
+
object = get_instance_of(object_or_klass)
|
29
|
+
matcher = allow_value(value).
|
30
|
+
for(attribute).
|
31
|
+
with_message(error_message_to_avoid)
|
32
|
+
assert_accepts(matcher, object)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Asserts that an Active Model model invalidates the passed
|
36
|
+
# <tt>value</tt> by making sure the <tt>error_message_to_expect</tt> is
|
37
|
+
# contained within the list of errors for that attribute.
|
38
|
+
#
|
39
|
+
# assert_bad_value(User.new, :email, "invalid")
|
40
|
+
# assert_bad_value(User.new, :ssn, "123", /length/)
|
41
|
+
#
|
42
|
+
# If a class is passed as the first argument, a new object will be
|
43
|
+
# instantiated before the assertion. If an instance variable exists with
|
44
|
+
# the same name as the class (underscored), that object will be used
|
45
|
+
# instead.
|
46
|
+
#
|
47
|
+
# assert_bad_value(User, :email, "invalid")
|
48
|
+
#
|
49
|
+
# product = Product.new(:tangible => true)
|
50
|
+
# assert_bad_value(product, :price, "0")
|
51
|
+
def assert_bad_value(object_or_klass, attribute, value,
|
52
|
+
error_message_to_expect = nil)
|
53
|
+
object = get_instance_of(object_or_klass)
|
54
|
+
matcher = allow_value(value).
|
55
|
+
for(attribute).
|
56
|
+
with_message(error_message_to_expect)
|
57
|
+
assert_rejects(matcher, object)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveModel # :nodoc:
|
3
|
+
module Helpers
|
4
|
+
def pretty_error_messages(obj) # :nodoc:
|
5
|
+
obj.errors.map do |a, m|
|
6
|
+
msg = "#{a} #{m}"
|
7
|
+
msg << " (#{obj.send(a).inspect})" unless a.to_sym == :base
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Helper method that determines the default error message used by Active
|
12
|
+
# Record. Works for both existing Rails 2.1 and Rails 2.2 with the newly
|
13
|
+
# introduced I18n module used for localization.
|
14
|
+
#
|
15
|
+
# default_error_message(:blank)
|
16
|
+
# default_error_message(:too_short, :count => 5)
|
17
|
+
# default_error_message(:too_long, :count => 60)
|
18
|
+
def default_error_message(key, values = {})
|
19
|
+
if Object.const_defined?(:I18n) # Rails >= 2.2
|
20
|
+
message = I18n.translate("activerecord.errors.messages.#{key}", values, :default => '')
|
21
|
+
if message.blank? # Rails 3 where the namespace has moved
|
22
|
+
message = I18n.translate("errors.messages.#{key}", values)
|
23
|
+
end
|
24
|
+
message
|
25
|
+
else # Rails <= 2.1.x
|
26
|
+
::ActiveRecord::Errors.default_error_messages[key] % values[:count]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveModel # :nodoc:
|
3
|
+
# = Macro test helpers for your active model models
|
4
|
+
#
|
5
|
+
# These helpers will test most of the validations and associations for your ActiveModel models.
|
6
|
+
#
|
7
|
+
# class UserTest < Test::Unit::TestCase
|
8
|
+
# should_validate_presence_of :name, :phone_number
|
9
|
+
# should_not_allow_values_for :phone_number, "abcd", "1234"
|
10
|
+
# should_allow_values_for :phone_number, "(123) 456-7890"
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# For all of these helpers, the last parameter may be a hash of options.
|
14
|
+
#
|
15
|
+
module Macros
|
16
|
+
include Matchers
|
17
|
+
|
18
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not present.
|
19
|
+
#
|
20
|
+
# Options:
|
21
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
22
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.blank')</tt>
|
23
|
+
#
|
24
|
+
# Example:
|
25
|
+
# should_validate_presence_of :name, :phone_number
|
26
|
+
#
|
27
|
+
def should_validate_presence_of(*attributes)
|
28
|
+
message = get_options!(attributes, :message)
|
29
|
+
|
30
|
+
attributes.each do |attribute|
|
31
|
+
matcher = validate_presence_of(attribute).with_message(message)
|
32
|
+
should matcher.description do
|
33
|
+
assert_accepts(matcher, subject)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not unique.
|
39
|
+
# Requires an existing model
|
40
|
+
#
|
41
|
+
# Options:
|
42
|
+
|
43
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
44
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.taken')</tt>
|
45
|
+
# * <tt>:scoped_to</tt> - field(s) to scope the uniqueness to.
|
46
|
+
# * <tt>:case_sensitive</tt> - whether or not uniqueness is defined by an
|
47
|
+
# exact match. Ignored by non-text attributes. Default = <tt>true</tt>
|
48
|
+
#
|
49
|
+
# Examples:
|
50
|
+
# should_validate_uniqueness_of :keyword, :username
|
51
|
+
# should_validate_uniqueness_of :name, :message => "O NOES! SOMEONE STOELED YER NAME!"
|
52
|
+
# should_validate_uniqueness_of :email, :scoped_to => :name
|
53
|
+
# should_validate_uniqueness_of :address, :scoped_to => [:first_name, :last_name]
|
54
|
+
# should_validate_uniqueness_of :email, :case_sensitive => false
|
55
|
+
#
|
56
|
+
def should_validate_uniqueness_of(*attributes)
|
57
|
+
message, scope, case_sensitive = get_options!(attributes, :message, :scoped_to, :case_sensitive)
|
58
|
+
scope = [*scope].compact
|
59
|
+
case_sensitive = true if case_sensitive.nil?
|
60
|
+
|
61
|
+
attributes.each do |attribute|
|
62
|
+
matcher = validate_uniqueness_of(attribute).
|
63
|
+
with_message(message).scoped_to(scope)
|
64
|
+
matcher = matcher.case_insensitive unless case_sensitive
|
65
|
+
should matcher.description do
|
66
|
+
assert_accepts(matcher, subject)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Ensures that the attribute cannot be set to the given values
|
72
|
+
#
|
73
|
+
# Options:
|
74
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
75
|
+
# Regexp or string. If omitted, the test will pass if there is ANY error in
|
76
|
+
# <tt>errors.on(:attribute)</tt>.
|
77
|
+
#
|
78
|
+
# Example:
|
79
|
+
# should_not_allow_values_for :isbn, "bad 1", "bad 2"
|
80
|
+
#
|
81
|
+
def should_not_allow_values_for(attribute, *bad_values)
|
82
|
+
message = get_options!(bad_values, :message)
|
83
|
+
bad_values.each do |value|
|
84
|
+
matcher = allow_value(value).for(attribute).with_message(message)
|
85
|
+
should "not #{matcher.description}" do
|
86
|
+
assert_rejects matcher, subject
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Ensures that the attribute can be set to the given values.
|
92
|
+
#
|
93
|
+
# Example:
|
94
|
+
# should_allow_values_for :isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0"
|
95
|
+
#
|
96
|
+
def should_allow_values_for(attribute, *good_values)
|
97
|
+
get_options!(good_values)
|
98
|
+
good_values.each do |value|
|
99
|
+
matcher = allow_value(value).for(attribute)
|
100
|
+
should matcher.description do
|
101
|
+
assert_accepts matcher, subject
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Ensures that the length of the attribute is in the given range
|
107
|
+
#
|
108
|
+
# Options:
|
109
|
+
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
110
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.too_short') % range.first</tt>
|
111
|
+
# * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
112
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.too_long') % range.last</tt>
|
113
|
+
#
|
114
|
+
# Example:
|
115
|
+
# should_ensure_length_in_range :password, (6..20)
|
116
|
+
#
|
117
|
+
def should_ensure_length_in_range(attribute, range, opts = {})
|
118
|
+
short_message, long_message = get_options!([opts],
|
119
|
+
:short_message,
|
120
|
+
:long_message)
|
121
|
+
matcher = ensure_length_of(attribute).
|
122
|
+
is_at_least(range.first).
|
123
|
+
with_short_message(short_message).
|
124
|
+
is_at_most(range.last).
|
125
|
+
with_long_message(long_message)
|
126
|
+
|
127
|
+
should matcher.description do
|
128
|
+
assert_accepts matcher, subject
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Ensures that the length of the attribute is at least a certain length
|
133
|
+
#
|
134
|
+
# Options:
|
135
|
+
# * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
136
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.too_short') % min_length</tt>
|
137
|
+
#
|
138
|
+
# Example:
|
139
|
+
# should_ensure_length_at_least :name, 3
|
140
|
+
#
|
141
|
+
def should_ensure_length_at_least(attribute, min_length, opts = {})
|
142
|
+
short_message = get_options!([opts], :short_message)
|
143
|
+
|
144
|
+
matcher = ensure_length_of(attribute).
|
145
|
+
is_at_least(min_length).
|
146
|
+
with_short_message(short_message)
|
147
|
+
|
148
|
+
should matcher.description do
|
149
|
+
assert_accepts matcher, subject
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Ensures that the length of the attribute is exactly a certain length
|
154
|
+
#
|
155
|
+
# Options:
|
156
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
157
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.wrong_length') % length</tt>
|
158
|
+
#
|
159
|
+
# Example:
|
160
|
+
# should_ensure_length_is :ssn, 9
|
161
|
+
#
|
162
|
+
def should_ensure_length_is(attribute, length, opts = {})
|
163
|
+
message = get_options!([opts], :message)
|
164
|
+
matcher = ensure_length_of(attribute).
|
165
|
+
is_equal_to(length).
|
166
|
+
with_message(message)
|
167
|
+
|
168
|
+
should matcher.description do
|
169
|
+
assert_accepts matcher, subject
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Ensure that the attribute is in the range specified
|
174
|
+
#
|
175
|
+
# Options:
|
176
|
+
# * <tt>:low_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
177
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.inclusion')</tt>
|
178
|
+
# * <tt>:high_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
179
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.inclusion')</tt>
|
180
|
+
#
|
181
|
+
# Example:
|
182
|
+
# should_ensure_value_in_range :age, (0..100)
|
183
|
+
#
|
184
|
+
def should_ensure_value_in_range(attribute, range, opts = {})
|
185
|
+
message, low_message, high_message = get_options!([opts],
|
186
|
+
:message,
|
187
|
+
:low_message,
|
188
|
+
:high_message)
|
189
|
+
matcher = ensure_inclusion_of(attribute).
|
190
|
+
in_range(range).
|
191
|
+
with_message(message).
|
192
|
+
with_low_message(low_message).
|
193
|
+
with_high_message(high_message)
|
194
|
+
should matcher.description do
|
195
|
+
assert_accepts matcher, subject
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Ensure that the attribute is numeric
|
200
|
+
#
|
201
|
+
# Options:
|
202
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
203
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.not_a_number')</tt>
|
204
|
+
#
|
205
|
+
# Example:
|
206
|
+
# should_validate_numericality_of :age
|
207
|
+
#
|
208
|
+
def should_validate_numericality_of(*attributes)
|
209
|
+
message = get_options!(attributes, :message)
|
210
|
+
attributes.each do |attribute|
|
211
|
+
matcher = validate_numericality_of(attribute).
|
212
|
+
with_message(message)
|
213
|
+
should matcher.description do
|
214
|
+
assert_accepts matcher, subject
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Ensure that the given class methods are defined on the model.
|
220
|
+
#
|
221
|
+
# should_have_class_methods :find, :destroy
|
222
|
+
#
|
223
|
+
def should_have_class_methods(*methods)
|
224
|
+
get_options!(methods)
|
225
|
+
klass = described_type
|
226
|
+
methods.each do |method|
|
227
|
+
should "respond to class method ##{method}" do
|
228
|
+
assert_respond_to klass, method, "#{klass.name} does not have class method #{method}"
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Ensure that the given instance methods are defined on the model.
|
234
|
+
#
|
235
|
+
# should_have_instance_methods :email, :name, :name=
|
236
|
+
#
|
237
|
+
def should_have_instance_methods(*methods)
|
238
|
+
get_options!(methods)
|
239
|
+
klass = described_type
|
240
|
+
methods.each do |method|
|
241
|
+
should "respond to instance method ##{method}" do
|
242
|
+
assert_respond_to klass.new, method, "#{klass.name} does not have instance method #{method}"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Ensures that the model cannot be saved if one of the attributes listed is not accepted.
|
248
|
+
#
|
249
|
+
# Options:
|
250
|
+
# * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
|
251
|
+
# Regexp or string. Default = <tt>I18n.translate('activemodel.errors.messages.accepted')</tt>
|
252
|
+
#
|
253
|
+
# Example:
|
254
|
+
# should_validate_acceptance_of :eula
|
255
|
+
#
|
256
|
+
def should_validate_acceptance_of(*attributes)
|
257
|
+
message = get_options!(attributes, :message)
|
258
|
+
|
259
|
+
attributes.each do |attribute|
|
260
|
+
matcher = validate_acceptance_of(attribute).with_message(message)
|
261
|
+
should matcher.description do
|
262
|
+
assert_accepts matcher, subject
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'shoulda/active_model/matchers/validation_matcher'
|
2
|
+
require 'shoulda/active_model/matchers/allow_value_matcher'
|
3
|
+
require 'shoulda/active_model/matchers/ensure_length_of_matcher'
|
4
|
+
require 'shoulda/active_model/matchers/ensure_inclusion_of_matcher'
|
5
|
+
require 'shoulda/active_model/matchers/validate_presence_of_matcher'
|
6
|
+
require 'shoulda/active_model/matchers/validate_format_of_matcher'
|
7
|
+
require 'shoulda/active_model/matchers/validate_uniqueness_of_matcher'
|
8
|
+
require 'shoulda/active_model/matchers/validate_acceptance_of_matcher'
|
9
|
+
require 'shoulda/active_model/matchers/validate_numericality_of_matcher'
|
10
|
+
|
11
|
+
module Shoulda # :nodoc:
|
12
|
+
module ActiveModel # :nodoc:
|
13
|
+
# = Matchers for your ActiveModel models
|
14
|
+
#
|
15
|
+
# These matchers will test most of the validations and associations for your
|
16
|
+
# ActiveModel models.
|
17
|
+
#
|
18
|
+
# describe User do
|
19
|
+
# it { should validate_presence_of(:name) }
|
20
|
+
# it { should validate_presence_of(:phone_number) }
|
21
|
+
# %w(abcd 1234).each do |value|
|
22
|
+
# it { should_not allow_value(value).for(:phone_number) }
|
23
|
+
# end
|
24
|
+
# it { should allow_value("(123) 456-7890").for(:phone_number) }
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
module Matchers
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module ActiveModel # :nodoc:
|
3
|
+
module Matchers
|
4
|
+
|
5
|
+
# Ensures that the attribute can be set to the given value.
|
6
|
+
#
|
7
|
+
# Options:
|
8
|
+
# * <tt>with_message</tt> - value the test expects to find in
|
9
|
+
# <tt>errors.on(:attribute)</tt>. Regexp or string. If omitted,
|
10
|
+
# the test looks for any errors in <tt>errors.on(:attribute)</tt>.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
# it { should_not allow_value('bad').for(:isbn) }
|
14
|
+
# it { should allow_value("isbn 1 2345 6789 0").for(:isbn) }
|
15
|
+
#
|
16
|
+
def allow_value(value)
|
17
|
+
AllowValueMatcher.new(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
class AllowValueMatcher # :nodoc:
|
21
|
+
include Helpers
|
22
|
+
|
23
|
+
def initialize(value)
|
24
|
+
@value = value
|
25
|
+
end
|
26
|
+
|
27
|
+
def for(attribute)
|
28
|
+
@attribute = attribute
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def with_message(message)
|
33
|
+
@expected_message = message if message
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def matches?(instance)
|
38
|
+
@instance = instance
|
39
|
+
if Symbol === @expected_message
|
40
|
+
@expected_message = default_error_message(@expected_message)
|
41
|
+
end
|
42
|
+
@instance.send("#{@attribute}=", @value)
|
43
|
+
!errors_match?
|
44
|
+
end
|
45
|
+
|
46
|
+
def failure_message
|
47
|
+
"Did not expect #{expectation}, got error: #{@matched_error}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def negative_failure_message
|
51
|
+
"Expected #{expectation}, got #{error_description}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def description
|
55
|
+
"allow #{@attribute} to be set to #{@value.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def errors_match?
|
61
|
+
@instance.valid?
|
62
|
+
@errors = @instance.errors[@attribute]
|
63
|
+
@errors = [@errors] unless @errors.is_a?(Array)
|
64
|
+
@expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors != [nil])
|
65
|
+
end
|
66
|
+
|
67
|
+
def errors_match_regexp?
|
68
|
+
if Regexp === @expected_message
|
69
|
+
@matched_error = @errors.detect { |e| e =~ @expected_message }
|
70
|
+
!@matched_error.nil?
|
71
|
+
else
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def errors_match_string?
|
77
|
+
if @errors.include?(@expected_message)
|
78
|
+
@matched_error = @expected_message
|
79
|
+
true
|
80
|
+
else
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def expectation
|
86
|
+
"errors " <<
|
87
|
+
(@expected_message ? "to include #{@expected_message.inspect} " : "") <<
|
88
|
+
"when #{@attribute} is set to #{@value.inspect}"
|
89
|
+
end
|
90
|
+
|
91
|
+
def error_description
|
92
|
+
if @instance.errors.empty?
|
93
|
+
"no errors"
|
94
|
+
else
|
95
|
+
"errors: #{pretty_error_messages(@instance)}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|