yetanothernguyen-shoulda-matchers 1.0.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- data/Appraisals +12 -0
- data/CONTRIBUTION_GUIDELINES.rdoc +10 -0
- data/Gemfile +5 -0
- data/MIT-LICENSE +22 -0
- data/README.rdoc +78 -0
- data/Rakefile +55 -0
- data/lib/shoulda-matchers.rb +1 -0
- data/lib/shoulda/matchers.rb +8 -0
- data/lib/shoulda/matchers/action_controller.rb +38 -0
- data/lib/shoulda/matchers/action_controller/assign_to_matcher.rb +114 -0
- data/lib/shoulda/matchers/action_controller/filter_param_matcher.rb +50 -0
- data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +62 -0
- data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +54 -0
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +99 -0
- data/lib/shoulda/matchers/action_controller/respond_with_content_type_matcher.rb +74 -0
- data/lib/shoulda/matchers/action_controller/respond_with_matcher.rb +85 -0
- data/lib/shoulda/matchers/action_controller/route_matcher.rb +93 -0
- data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +98 -0
- data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +94 -0
- data/lib/shoulda/matchers/action_mailer.rb +22 -0
- data/lib/shoulda/matchers/action_mailer/have_sent_email.rb +166 -0
- data/lib/shoulda/matchers/active_model.rb +33 -0
- data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +83 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +110 -0
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +87 -0
- data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +141 -0
- data/lib/shoulda/matchers/active_model/helpers.rb +29 -0
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +41 -0
- data/lib/shoulda/matchers/active_model/validate_format_of_matcher.rb +65 -0
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +39 -0
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +61 -0
- data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +148 -0
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +56 -0
- data/lib/shoulda/matchers/active_record.rb +24 -0
- data/lib/shoulda/matchers/active_record/association_matcher.rb +226 -0
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +169 -0
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +112 -0
- data/lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb +59 -0
- data/lib/shoulda/matchers/assertion_error.rb +11 -0
- data/lib/shoulda/matchers/integrations/rspec.rb +38 -0
- data/lib/shoulda/matchers/integrations/test_unit.rb +54 -0
- data/lib/shoulda/matchers/version.rb +5 -0
- metadata +169 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Matchers
|
3
|
+
module ActionController # :nodoc:
|
4
|
+
|
5
|
+
# Ensures that the flash contains the given value. Can be a String, a
|
6
|
+
# Regexp, or nil (indicating that the flash should not be set).
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# it { should set_the_flash }
|
11
|
+
# it { should set_the_flash.to("Thank you for placing this order.") }
|
12
|
+
# it { should set_the_flash.to(/created/i) }
|
13
|
+
# it { should set_the_flash.to(/logged in/i).now }
|
14
|
+
# it { should_not set_the_flash }
|
15
|
+
def set_the_flash
|
16
|
+
SetTheFlashMatcher.new
|
17
|
+
end
|
18
|
+
|
19
|
+
class SetTheFlashMatcher # :nodoc:
|
20
|
+
|
21
|
+
def to(value)
|
22
|
+
@value = value
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def now
|
27
|
+
@now = true
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def matches?(controller)
|
32
|
+
@controller = controller
|
33
|
+
sets_the_flash? && string_value_matches? && regexp_value_matches?
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :failure_message, :negative_failure_message
|
37
|
+
|
38
|
+
def description
|
39
|
+
description = "set the flash"
|
40
|
+
description << " to #{@value.inspect}" unless @value.nil?
|
41
|
+
description
|
42
|
+
end
|
43
|
+
|
44
|
+
def failure_message
|
45
|
+
"Expected #{expectation}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def negative_failure_message
|
49
|
+
"Did not expect #{expectation}"
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def sets_the_flash?
|
55
|
+
!flash.blank?
|
56
|
+
end
|
57
|
+
|
58
|
+
def string_value_matches?
|
59
|
+
return true unless String === @value
|
60
|
+
flash.to_hash.values.any? {|value| value == @value }
|
61
|
+
end
|
62
|
+
|
63
|
+
def regexp_value_matches?
|
64
|
+
return true unless Regexp === @value
|
65
|
+
flash.to_hash.values.any? {|value| value =~ @value }
|
66
|
+
end
|
67
|
+
|
68
|
+
def flash
|
69
|
+
return @flash if @flash
|
70
|
+
@flash = @controller.flash.dup
|
71
|
+
@flash.sweep unless @now
|
72
|
+
@flash
|
73
|
+
end
|
74
|
+
|
75
|
+
def expectation
|
76
|
+
expectation = "the flash#{".now" if @now} to be set"
|
77
|
+
expectation << " to #{@value.inspect}" unless @value.nil?
|
78
|
+
expectation << ", but #{flash_description}"
|
79
|
+
expectation
|
80
|
+
end
|
81
|
+
|
82
|
+
def flash_description
|
83
|
+
if flash.blank?
|
84
|
+
"no flash was set"
|
85
|
+
else
|
86
|
+
"was #{flash.inspect}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'shoulda/matchers/action_mailer/have_sent_email'
|
2
|
+
|
3
|
+
module Shoulda
|
4
|
+
module Matchers
|
5
|
+
# = Matchers for your mailers
|
6
|
+
#
|
7
|
+
# This matcher will test that email is sent properly
|
8
|
+
#
|
9
|
+
# describe User do
|
10
|
+
# it { should have_sent_email.with_subject(/is spam$/) }
|
11
|
+
# it { should have_sent_email.from('do-not-reply@example.com') }
|
12
|
+
# it { should have_sent_email.with_body(/is spam\./) }
|
13
|
+
# it { should have_sent_email.to('myself@me.com') }
|
14
|
+
# it { should have_sent_email.with_subject(/spam/).
|
15
|
+
# from('do-not-reply@example.com').
|
16
|
+
# with_body(/spam/).
|
17
|
+
# to('myself@me.com') }
|
18
|
+
# end
|
19
|
+
module ActionMailer
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Matchers
|
3
|
+
module ActionMailer # :nodoc:
|
4
|
+
|
5
|
+
# The right email is sent.
|
6
|
+
#
|
7
|
+
# it { should have_sent_email.with_subject(/is spam$/) }
|
8
|
+
# it { should have_sent_email.from('do-not-reply@example.com') }
|
9
|
+
# it { should have_sent_email.with_body(/is spam\./) }
|
10
|
+
# it { should have_sent_email.to('myself@me.com') }
|
11
|
+
# it { should have_sent_email.with_part('text/html', /HTML spam/) }
|
12
|
+
# it { should have_sent_email.with_subject(/spam/).
|
13
|
+
# from('do-not-reply@example.com').
|
14
|
+
# with_body(/spam/).
|
15
|
+
# to('myself@me.com') }
|
16
|
+
def have_sent_email
|
17
|
+
HaveSentEmailMatcher.new
|
18
|
+
end
|
19
|
+
|
20
|
+
class HaveSentEmailMatcher # :nodoc:
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_subject(email_subject)
|
26
|
+
@email_subject = email_subject
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def from(sender)
|
31
|
+
@sender = sender
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_body(body)
|
36
|
+
@body = body
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_part(content_type, body)
|
41
|
+
@parts ||= []
|
42
|
+
@parts << [/#{Regexp.escape(content_type)}/, body, content_type]
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def to(recipient)
|
47
|
+
@recipient = recipient
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def multipart(flag = true)
|
52
|
+
@multipart = !!flag
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def matches?(subject)
|
57
|
+
::ActionMailer::Base.deliveries.each do |mail|
|
58
|
+
@subject_failed = !regexp_or_string_match(mail.subject, @email_subject) if @email_subject
|
59
|
+
@parts_failed = !parts_match(mail, @parts) if @parts
|
60
|
+
@body_failed = !body_match(mail, @body) if @body
|
61
|
+
@sender_failed = !regexp_or_string_match_in_array(mail.from, @sender) if @sender
|
62
|
+
@recipient_failed = !regexp_or_string_match_in_array(mail.to, @recipient) if @recipient
|
63
|
+
@multipart_failed = mail.multipart? != @multipart if defined?(@multipart)
|
64
|
+
return true unless anything_failed?
|
65
|
+
end
|
66
|
+
|
67
|
+
false
|
68
|
+
end
|
69
|
+
|
70
|
+
def failure_message
|
71
|
+
"Expected #{expectation}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def negative_failure_message
|
75
|
+
"Did not expect #{expectation}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def description
|
79
|
+
description = "send an email"
|
80
|
+
description << " with a subject of #{@email_subject.inspect}" if @email_subject
|
81
|
+
description << " containing #{@body.inspect}" if @body
|
82
|
+
@parts.each do |_, body, content_type|
|
83
|
+
description << " having a #{content_type} part containing #{body.inspect}"
|
84
|
+
end if @parts
|
85
|
+
description << " from #{@sender.inspect}" if @sender
|
86
|
+
description << " to #{@recipient.inspect}" if @recipient
|
87
|
+
description
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def expectation
|
93
|
+
expectation = "sent email"
|
94
|
+
expectation << " with subject #{@email_subject.inspect}" if @subject_failed
|
95
|
+
expectation << " with body #{@body.inspect}" if @body_failed
|
96
|
+
@parts.each do |_, body, content_type|
|
97
|
+
expectation << " with a #{content_type} part containing #{body}"
|
98
|
+
end if @parts && @parts_failed
|
99
|
+
expectation << " from #{@sender.inspect}" if @sender_failed
|
100
|
+
expectation << " to #{@recipient.inspect}" if @recipient_failed
|
101
|
+
expectation << " #{'not ' if !@multipart}being multipart" if @multipart_failed
|
102
|
+
expectation << "\nDeliveries:\n#{inspect_deliveries}"
|
103
|
+
end
|
104
|
+
|
105
|
+
def inspect_deliveries
|
106
|
+
::ActionMailer::Base.deliveries.map do |delivery|
|
107
|
+
"#{delivery.subject.inspect} to #{delivery.to.inspect}"
|
108
|
+
end.join("\n")
|
109
|
+
end
|
110
|
+
|
111
|
+
def anything_failed?
|
112
|
+
@subject_failed || @body_failed || @parts_failed || @sender_failed ||
|
113
|
+
@recipient_failed || @multipart_failed
|
114
|
+
end
|
115
|
+
|
116
|
+
def regexp_or_string_match(a_string, a_regexp_or_string)
|
117
|
+
case a_regexp_or_string
|
118
|
+
when Regexp
|
119
|
+
a_string =~ a_regexp_or_string
|
120
|
+
when String
|
121
|
+
a_string == a_regexp_or_string
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def regexp_or_string_match_in_array(an_array, a_regexp_or_string)
|
126
|
+
case a_regexp_or_string
|
127
|
+
when Regexp
|
128
|
+
an_array.any? { |string| string =~ a_regexp_or_string }
|
129
|
+
when String
|
130
|
+
an_array.include?(a_regexp_or_string)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def body_match(mail, a_regexp_or_string)
|
135
|
+
# Mail objects instantiated by ActionMailer3 return a blank
|
136
|
+
# body if the e-mail is multipart. TMail concatenates the
|
137
|
+
# String representation of each part instead.
|
138
|
+
if mail.body.blank? && mail.multipart?
|
139
|
+
part_match(mail, /^text\//, a_regexp_or_string)
|
140
|
+
else
|
141
|
+
regexp_or_string_match(mail.body, a_regexp_or_string)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def parts_match(mail, parts)
|
146
|
+
return false if mail.parts.empty?
|
147
|
+
|
148
|
+
parts.all? do |content_type, match, _|
|
149
|
+
part_match(mail, content_type, match)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def part_match(mail, content_type, a_regexp_or_string)
|
154
|
+
matching = mail.parts.select {|p| p.content_type =~ content_type}
|
155
|
+
return false if matching.empty?
|
156
|
+
|
157
|
+
matching.all? do |part|
|
158
|
+
regexp_or_string_match(part.body, a_regexp_or_string)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'shoulda/matchers/active_model/helpers'
|
2
|
+
require 'shoulda/matchers/active_model/validation_matcher'
|
3
|
+
require 'shoulda/matchers/active_model/allow_value_matcher'
|
4
|
+
require 'shoulda/matchers/active_model/ensure_length_of_matcher'
|
5
|
+
require 'shoulda/matchers/active_model/ensure_inclusion_of_matcher'
|
6
|
+
require 'shoulda/matchers/active_model/validate_presence_of_matcher'
|
7
|
+
require 'shoulda/matchers/active_model/validate_format_of_matcher'
|
8
|
+
require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher'
|
9
|
+
require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
|
10
|
+
require 'shoulda/matchers/active_model/validate_numericality_of_matcher'
|
11
|
+
require 'shoulda/matchers/active_model/allow_mass_assignment_of_matcher'
|
12
|
+
|
13
|
+
|
14
|
+
module Shoulda
|
15
|
+
module Matchers
|
16
|
+
# = Matchers for your active record models
|
17
|
+
#
|
18
|
+
# These matchers will test most of the validations of ActiveModel::Validations.
|
19
|
+
#
|
20
|
+
# describe User do
|
21
|
+
# it { should validate_presence_of(:name) }
|
22
|
+
# it { should validate_presence_of(:phone_number) }
|
23
|
+
# %w(abcd 1234).each do |value|
|
24
|
+
# it { should_not allow_value(value).for(:phone_number) }
|
25
|
+
# end
|
26
|
+
# it { should allow_value("(123) 456-7890").for(:phone_number) }
|
27
|
+
# it { should_not allow_mass_assignment_of(:password) }
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
module ActiveModel
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel # :nodoc:
|
4
|
+
|
5
|
+
# Ensures that the attribute can be set on mass update.
|
6
|
+
#
|
7
|
+
# it { should_not allow_mass_assignment_of(:password) }
|
8
|
+
# it { should allow_mass_assignment_of(:first_name) }
|
9
|
+
#
|
10
|
+
def allow_mass_assignment_of(value)
|
11
|
+
AllowMassAssignmentOfMatcher.new(value)
|
12
|
+
end
|
13
|
+
|
14
|
+
class AllowMassAssignmentOfMatcher # :nodoc:
|
15
|
+
|
16
|
+
def initialize(attribute)
|
17
|
+
@attribute = attribute.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def matches?(subject)
|
21
|
+
@subject = subject
|
22
|
+
if attr_mass_assignable?
|
23
|
+
if whitelisting?
|
24
|
+
@negative_failure_message = "#{@attribute} was made accessible"
|
25
|
+
else
|
26
|
+
if protected_attributes.empty?
|
27
|
+
@negative_failure_message = "no attributes were protected"
|
28
|
+
else
|
29
|
+
@negative_failure_message = "#{class_name} is protecting " <<
|
30
|
+
"#{protected_attributes.to_a.to_sentence}, " <<
|
31
|
+
"but not #{@attribute}."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
true
|
35
|
+
else
|
36
|
+
if whitelisting?
|
37
|
+
@failure_message =
|
38
|
+
"Expected #{@attribute} to be accessible"
|
39
|
+
else
|
40
|
+
@failure_message =
|
41
|
+
"Did not expect #{@attribute} to be protected"
|
42
|
+
end
|
43
|
+
false
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :failure_message, :negative_failure_message
|
48
|
+
|
49
|
+
def description
|
50
|
+
"allow mass assignment of #{@attribute}"
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def protected_attributes
|
56
|
+
@protected_attributes ||= (@subject.class.protected_attributes || [])
|
57
|
+
end
|
58
|
+
|
59
|
+
def accessible_attributes
|
60
|
+
@accessible_attributes ||= (@subject.class.accessible_attributes || [])
|
61
|
+
end
|
62
|
+
|
63
|
+
def whitelisting?
|
64
|
+
!accessible_attributes.empty?
|
65
|
+
end
|
66
|
+
|
67
|
+
def attr_mass_assignable?
|
68
|
+
if whitelisting?
|
69
|
+
accessible_attributes.include?(@attribute)
|
70
|
+
else
|
71
|
+
!protected_attributes.include?(@attribute)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def class_name
|
76
|
+
@subject.class.name
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Shoulda # :nodoc:
|
2
|
+
module Matchers
|
3
|
+
module ActiveModel # :nodoc:
|
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 = errors_for_attribute(@instance, @attribute)
|
63
|
+
@errors = [@errors] unless @errors.is_a?(Array)
|
64
|
+
@expected_message ? (errors_match_regexp? || errors_match_string?) : (@errors.compact.any?)
|
65
|
+
end
|
66
|
+
|
67
|
+
def errors_for_attribute(instance, attribute)
|
68
|
+
if instance.errors.respond_to?(:[])
|
69
|
+
instance.errors[attribute]
|
70
|
+
else
|
71
|
+
instance.errors.on(attribute)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def errors_match_regexp?
|
76
|
+
if Regexp === @expected_message
|
77
|
+
@matched_error = @errors.detect { |e| e =~ @expected_message }
|
78
|
+
!@matched_error.nil?
|
79
|
+
else
|
80
|
+
false
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def errors_match_string?
|
85
|
+
if @errors.include?(@expected_message)
|
86
|
+
@matched_error = @expected_message
|
87
|
+
true
|
88
|
+
else
|
89
|
+
false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def expectation
|
94
|
+
"errors " <<
|
95
|
+
(@expected_message ? "to include #{@expected_message.inspect} " : "") <<
|
96
|
+
"when #{@attribute} is set to #{@value.inspect}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def error_description
|
100
|
+
if @instance.errors.empty?
|
101
|
+
"no errors"
|
102
|
+
else
|
103
|
+
"errors: #{pretty_error_messages(@instance)}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|