ward 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +28 -0
- data/LICENSE +19 -0
- data/README.markdown +99 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/features/acceptance_matcher.feature +78 -0
- data/features/attribute_keyword.feature +13 -0
- data/features/close_to_matcher.feature +130 -0
- data/features/context_arguments.feature +47 -0
- data/features/equal_to_matcher.feature +25 -0
- data/features/error_messages.feature +69 -0
- data/features/external_validation.feature +15 -0
- data/features/has_matcher.feature +72 -0
- data/features/has_matcher_initialized_with_expectation.feature +94 -0
- data/features/has_matcher_relativities.feature +171 -0
- data/features/include_matcher.feature +28 -0
- data/features/is_keyword.feature +42 -0
- data/features/is_not_keyword.feature +62 -0
- data/features/match_matcher.feature +49 -0
- data/features/multiple_validators.feature +29 -0
- data/features/nil_matcher.feature +25 -0
- data/features/predicate_matcher.feature +23 -0
- data/features/present_matcher.feature +59 -0
- data/features/satisfy_matcher.feature +80 -0
- data/features/scenario_validation.feature +81 -0
- data/features/step_definitions/external_validation_steps.rb +69 -0
- data/features/step_definitions/generic_validation_steps.rb +33 -0
- data/features/step_definitions/object_definition_steps.rb +43 -0
- data/features/support/env.rb +12 -0
- data/features/support/object_builder.rb +33 -0
- data/features/support/struct.rb +38 -0
- data/lang/en.yml +56 -0
- data/lib/ward.rb +26 -0
- data/lib/ward/context.rb +70 -0
- data/lib/ward/context_chain.rb +87 -0
- data/lib/ward/dsl.rb +7 -0
- data/lib/ward/dsl/validation_block.rb +73 -0
- data/lib/ward/dsl/validation_builder.rb +190 -0
- data/lib/ward/errors.rb +213 -0
- data/lib/ward/matchers.rb +97 -0
- data/lib/ward/matchers/acceptance.rb +43 -0
- data/lib/ward/matchers/close_to.rb +60 -0
- data/lib/ward/matchers/equal_to.rb +33 -0
- data/lib/ward/matchers/has.rb +283 -0
- data/lib/ward/matchers/include.rb +54 -0
- data/lib/ward/matchers/match.rb +29 -0
- data/lib/ward/matchers/matcher.rb +68 -0
- data/lib/ward/matchers/nil.rb +30 -0
- data/lib/ward/matchers/predicate.rb +31 -0
- data/lib/ward/matchers/present.rb +56 -0
- data/lib/ward/matchers/satisfy.rb +65 -0
- data/lib/ward/spec.rb +17 -0
- data/lib/ward/spec/matcher_matcher.rb +114 -0
- data/lib/ward/support.rb +7 -0
- data/lib/ward/support/basic_object.rb +55 -0
- data/lib/ward/support/result.rb +49 -0
- data/lib/ward/validator.rb +147 -0
- data/lib/ward/validator_set.rb +115 -0
- data/lib/ward/version.rb +3 -0
- data/spec/lib/has_matcher_relativity_examples.rb +15 -0
- data/spec/lib/have_public_method_defined.rb +22 -0
- data/spec/rcov.opts +8 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/ward/context_chain_spec.rb +178 -0
- data/spec/ward/context_spec.rb +57 -0
- data/spec/ward/dsl/validation_block_spec.rb +27 -0
- data/spec/ward/dsl/validation_builder_spec.rb +212 -0
- data/spec/ward/errors_spec.rb +149 -0
- data/spec/ward/matchers/acceptance_spec.rb +16 -0
- data/spec/ward/matchers/close_to_spec.rb +57 -0
- data/spec/ward/matchers/equal_to_spec.rb +16 -0
- data/spec/ward/matchers/has_spec.rb +175 -0
- data/spec/ward/matchers/include_spec.rb +41 -0
- data/spec/ward/matchers/match_spec.rb +21 -0
- data/spec/ward/matchers/matcher_spec.rb +54 -0
- data/spec/ward/matchers/nil_spec.rb +16 -0
- data/spec/ward/matchers/predicate_spec.rb +19 -0
- data/spec/ward/matchers/present_spec.rb +16 -0
- data/spec/ward/matchers/satisfy_spec.rb +68 -0
- data/spec/ward/matchers_spec.rb +51 -0
- data/spec/ward/spec/have_public_method_defined_spec.rb +31 -0
- data/spec/ward/spec/matcher_matcher_spec.rb +217 -0
- data/spec/ward/validator_set_spec.rb +178 -0
- data/spec/ward/validator_spec.rb +264 -0
- data/tasks/features.rake +15 -0
- data/tasks/rcov.rake +24 -0
- data/tasks/spec.rake +18 -0
- data/tasks/yard.rake +9 -0
- data/ward.gemspec +176 -0
- metadata +239 -0
data/lib/ward/dsl.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Ward
|
2
|
+
module DSL
|
3
|
+
# Creates one or more validators using a block.
|
4
|
+
#
|
5
|
+
# @see Ward::ValidatorSet.build
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
#
|
9
|
+
# # Builds a validation set with two validators
|
10
|
+
# #
|
11
|
+
# # * One for "author.name" and the EqualTo matcher.
|
12
|
+
# # * One for "title" with the Match matcher.
|
13
|
+
#
|
14
|
+
# ValidationBlock.new do |object|
|
15
|
+
# object.author.name.is.equal_to('Michael Scarn')
|
16
|
+
# object.title.match(/something/)
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
class ValidationBlock < Support::BasicObject
|
20
|
+
|
21
|
+
# Creates a new ValidationBlock instance.
|
22
|
+
#
|
23
|
+
# NOTE: Providing an existing ValidatorSet will result in a copy of that
|
24
|
+
# set being mutated; the original will not be changed.
|
25
|
+
#
|
26
|
+
# @param [Ward::ValidatorSet] set
|
27
|
+
# A ValidatorSet to which the built validators should be added.
|
28
|
+
#
|
29
|
+
def initialize(set = nil, &block)
|
30
|
+
@set = if set.nil? then Ward::ValidatorSet.new else set.dup end
|
31
|
+
run(&block) if block
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the ValidatorSet created by the DSL.
|
35
|
+
#
|
36
|
+
# @return [Ward::ValidatorSet]
|
37
|
+
#
|
38
|
+
def to_validator_set
|
39
|
+
@set
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# Runs a block, creating the appropriate validators.
|
45
|
+
#
|
46
|
+
# @todo Ensure that each matcher was correctly set up.
|
47
|
+
#
|
48
|
+
def run
|
49
|
+
@builders = []
|
50
|
+
yield self
|
51
|
+
@set.merge!(@builders.map { |builder| builder.to_validator })
|
52
|
+
@builders = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# Provides the DSL.
|
56
|
+
#
|
57
|
+
# Will take the given message and creates a new ValidationBuilder.
|
58
|
+
#
|
59
|
+
# @return [Ward::DSL::ValidationBuilder]
|
60
|
+
# Returns the created builder.
|
61
|
+
#
|
62
|
+
def method_missing(method, *extra_args, &block)
|
63
|
+
raise 'ValidationBlock can only be used when provided ' \
|
64
|
+
'with a block' if @builders.nil?
|
65
|
+
|
66
|
+
builder = ValidationBuilder.new.__send__(method, *extra_args, &block)
|
67
|
+
@builders.push(builder)
|
68
|
+
builder
|
69
|
+
end
|
70
|
+
|
71
|
+
end # ValidationBlock
|
72
|
+
end # DSL
|
73
|
+
end # Ward
|
@@ -0,0 +1,190 @@
|
|
1
|
+
module Ward
|
2
|
+
module DSL
|
3
|
+
# Creates a single {Validator}. Any message received which doesn't
|
4
|
+
# correspond with a matcher will be assumed to be part of the context.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
#
|
8
|
+
# # Builds a validation whose context is "author.name" and uses the
|
9
|
+
# # EqualTo matcher to ensure that the "author.name" is "Michel Scarn".
|
10
|
+
#
|
11
|
+
# ValidationBuilder.new.author.name.is.equal_to('Michael Scarn')
|
12
|
+
#
|
13
|
+
class ValidationBuilder < Support::BasicObject
|
14
|
+
|
15
|
+
# Creates a new ValidationBuilder instance.
|
16
|
+
#
|
17
|
+
def initialize
|
18
|
+
@context = Ward::ContextChain.new
|
19
|
+
@matcher, @message, @scenarios, @negative = nil, nil, nil, false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the error message to be used if the validation fails.
|
23
|
+
#
|
24
|
+
# This can be one of three possibilities:
|
25
|
+
#
|
26
|
+
# * A Hash. If the matcher used returns a different error state in
|
27
|
+
# order to provide more details about what failed (such as the Has
|
28
|
+
# matcher), you may provide a hash with custom error messages for
|
29
|
+
# each error state.
|
30
|
+
#
|
31
|
+
# * A String which will be used whenever the validation fails,
|
32
|
+
# regardless of what went wrong.
|
33
|
+
#
|
34
|
+
# * nil (default). The validation will use the default error message
|
35
|
+
# for the matcher.
|
36
|
+
#
|
37
|
+
# @param [Hash{Symbol => String}, String, nil] message
|
38
|
+
#
|
39
|
+
# @return [Ward::DSL::ValidatorBuilder]
|
40
|
+
# Returns self.
|
41
|
+
#
|
42
|
+
# @example Setting an explicit error message.
|
43
|
+
#
|
44
|
+
# validate do |person|
|
45
|
+
# person.name.is.present.message('You must enter a name!')
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# @example Setting an explicit error message with a Hash.
|
49
|
+
#
|
50
|
+
# validate do |person|
|
51
|
+
# person.name.length.is(1..50).message(
|
52
|
+
# :too_short => "Your name must be at least 1 character long",
|
53
|
+
# :too_long => "That's an interesting name!"
|
54
|
+
# )
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
def message(message)
|
58
|
+
@message = message
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets the name to be used for the context in error messages.
|
63
|
+
#
|
64
|
+
# When Ward generates error messages for you, it determines the
|
65
|
+
# 'context name' by joining the method names; for example 'name.length'
|
66
|
+
# becomes 'name length'.
|
67
|
+
#
|
68
|
+
# This isn't much use if you want to support languages other than
|
69
|
+
# English in your application, so the 'context' method allows you to set
|
70
|
+
# a custom string to be used. You may provide a String, in which case it
|
71
|
+
# will be used literally, a Hash of +language => String+, or a Symbol
|
72
|
+
# identifying a string to be used from a language file.
|
73
|
+
#
|
74
|
+
# See the localisation documentation for more examples.
|
75
|
+
#
|
76
|
+
def context(name)
|
77
|
+
@context_name = name
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
# Sets the scenarios under which the built validator should run.
|
82
|
+
#
|
83
|
+
# @param [Symbol, ...] scenarios
|
84
|
+
# The scenarios as Symbols.
|
85
|
+
#
|
86
|
+
# @return [Ward::DSL::ValidatorBuilder]
|
87
|
+
# Returns self.
|
88
|
+
#
|
89
|
+
def scenarios(*scenarios)
|
90
|
+
@scenarios = scenarios.flatten
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
94
|
+
alias_method :scenario, :scenarios
|
95
|
+
|
96
|
+
# Adds an attribute to the context chain.
|
97
|
+
#
|
98
|
+
# Useful your classes have attribute which you want to validate, and
|
99
|
+
# their name conflicts with a method on the validator DSL (e.g.
|
100
|
+
# "message").
|
101
|
+
#
|
102
|
+
# @param [Symbol] attribute
|
103
|
+
# The attribute to be validated.
|
104
|
+
#
|
105
|
+
# @return [Ward::DSL::ValidatorBuilder]
|
106
|
+
# Returns self.
|
107
|
+
#
|
108
|
+
def attribute(attribute, *args, &block)
|
109
|
+
@context << Ward::Context.new(attribute, *args, &block)
|
110
|
+
self
|
111
|
+
end
|
112
|
+
|
113
|
+
# Set this as a positive expectation. Can be omitted.
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# object.name.is.blank
|
117
|
+
#
|
118
|
+
# @return [Ward::DSL::ValidatorBuilder]
|
119
|
+
# Returns self.
|
120
|
+
#
|
121
|
+
def is(*args)
|
122
|
+
equal_to(*args) unless args.empty?
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
# Set this as a negative expectation.
|
127
|
+
#
|
128
|
+
# @example
|
129
|
+
# object.name.is_not.blank
|
130
|
+
#
|
131
|
+
# @return [Ward::DSL::ValidatorBuilder]
|
132
|
+
# Returns self.
|
133
|
+
#
|
134
|
+
def is_not(*args)
|
135
|
+
@negative = true
|
136
|
+
is(*args)
|
137
|
+
end
|
138
|
+
|
139
|
+
alias_method :does_not, :is_not
|
140
|
+
|
141
|
+
# Provides the DSL.
|
142
|
+
#
|
143
|
+
# Will take the given message and use it to customise the matcher (if
|
144
|
+
# one is set), set a matcher, or extend the context.
|
145
|
+
#
|
146
|
+
# @return [Ward::DSL::ValidatorBuilder]
|
147
|
+
# Returns self.
|
148
|
+
#
|
149
|
+
def method_missing(method, *args, &block)
|
150
|
+
# I'd normally shy away from using method_missing, but there's no
|
151
|
+
# good alternative here since a user may register their own matchers
|
152
|
+
# later in the load process.
|
153
|
+
|
154
|
+
if @matcher
|
155
|
+
@matcher.__send__(method, *args, &block)
|
156
|
+
elsif Ward::Matchers.matchers.has_key?(method)
|
157
|
+
@matcher = Ward::Matchers.matchers[method].new(*args, &block)
|
158
|
+
elsif method.to_s =~ /\?$/
|
159
|
+
@matcher = Ward::Matchers::Predicate.new(method, *args, &block)
|
160
|
+
else
|
161
|
+
attribute(method, *args, &block)
|
162
|
+
end
|
163
|
+
|
164
|
+
self
|
165
|
+
end
|
166
|
+
|
167
|
+
# Converts the builder to a Validator instance.
|
168
|
+
#
|
169
|
+
# @return [Ward::Validator]
|
170
|
+
#
|
171
|
+
# @raise [IncompleteValidation]
|
172
|
+
# An IncompleteValidationError will be raised if builder does not have
|
173
|
+
# all of the needed information in order to create the necessary
|
174
|
+
# validation (for example, if no matcher has been set).
|
175
|
+
#
|
176
|
+
# @todo
|
177
|
+
# More descriptive error messages.
|
178
|
+
#
|
179
|
+
def to_validator
|
180
|
+
raise Ward::IncompleteValidator,
|
181
|
+
'Validator was missing a matcher' if @matcher.nil?
|
182
|
+
|
183
|
+
Ward::Validator.new(@context, @matcher, :message => @message,
|
184
|
+
:scenarios => @scenarios, :negative => @negative,
|
185
|
+
:context_name => @context_name)
|
186
|
+
end
|
187
|
+
|
188
|
+
end # Validate
|
189
|
+
end # DSL
|
190
|
+
end # Ward
|
data/lib/ward/errors.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
module Ward
|
2
|
+
# Holds errors associated with a valid? call.
|
3
|
+
#
|
4
|
+
class Errors
|
5
|
+
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Returns a localisation message.
|
11
|
+
#
|
12
|
+
# @param [keys] String
|
13
|
+
# The key of the message to be returned from the current language
|
14
|
+
# file.
|
15
|
+
#
|
16
|
+
# @return [String, nil]
|
17
|
+
# Returns the message or nil if it couldn't be found.
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
#
|
21
|
+
# Ward::Errors.message('has.eql.positive')
|
22
|
+
# # => '%{context} should have %{expected} %{collection}'
|
23
|
+
#
|
24
|
+
# @example Passing multiple keys as fallbacks.
|
25
|
+
#
|
26
|
+
# Ward::Errors.message(
|
27
|
+
# 'does.not.exist', 'has.eql.negative', 'has.eql.positive')
|
28
|
+
#
|
29
|
+
# # => '%{context} should not have %{expected} %{collection}'
|
30
|
+
#
|
31
|
+
def message(*keys)
|
32
|
+
messages[ keys.detect { |key| messages.has_key?(key) } ]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the unformatted error message for a matcher.
|
36
|
+
#
|
37
|
+
# @param [Ward::Matchers::Matcher] matcher
|
38
|
+
# The matcher.
|
39
|
+
# @param [Boolean] negative
|
40
|
+
# Whether to return a negative message, rather than a positive.
|
41
|
+
# @param [nil, Symbol, String] key
|
42
|
+
# If a string is supplied, +error_for+ will assume that the string
|
43
|
+
# should be used as the error. A symbol will be assumed to be a 'key'
|
44
|
+
# from the language file, while nil will result in the validator using
|
45
|
+
# the default error message for the matcher.
|
46
|
+
#
|
47
|
+
# @return [String]
|
48
|
+
#
|
49
|
+
def error_for(matcher, negative, key = nil)
|
50
|
+
return key if key.is_a?(String)
|
51
|
+
|
52
|
+
language_key = if key.nil?
|
53
|
+
"#{matcher.class.error_id}."
|
54
|
+
else
|
55
|
+
"#{matcher.class.error_id}.#{key}."
|
56
|
+
end
|
57
|
+
|
58
|
+
language_key << (negative ? 'negative' : 'positive')
|
59
|
+
|
60
|
+
message(language_key) || '%{context} is invalid'
|
61
|
+
end
|
62
|
+
|
63
|
+
# Receives an array and formats it nicely, assuming that only one value
|
64
|
+
# is expected.
|
65
|
+
#
|
66
|
+
# @example One member
|
67
|
+
# format_exclusive_list([1]) # => '1'
|
68
|
+
#
|
69
|
+
# @example Two members
|
70
|
+
# format_exclusive_list([1, 2]) # => '1 or 2'
|
71
|
+
#
|
72
|
+
# @example Many members
|
73
|
+
# format_exclusive_list([1, 2, 3]) # => '1, 2, or 3'
|
74
|
+
#
|
75
|
+
# @param [Enumerable] list
|
76
|
+
# The list to be formatted.
|
77
|
+
#
|
78
|
+
# @return [String]
|
79
|
+
#
|
80
|
+
def format_exclusive_list(list)
|
81
|
+
format_list(list, message('generic.exclusive_conjunction'))
|
82
|
+
end
|
83
|
+
|
84
|
+
# Receives an array and formats it nicely, assuming that all values are
|
85
|
+
# expected.
|
86
|
+
#
|
87
|
+
# @example One member
|
88
|
+
# format_inclusive_list([1]) # => '1'
|
89
|
+
#
|
90
|
+
# @example Two members
|
91
|
+
# format_inclusive_list([1, 2]) # => '1 and 2'
|
92
|
+
#
|
93
|
+
# @example Many members
|
94
|
+
# format_inclusive_list([1, 2, 3]) # => '1, 2, and 3'
|
95
|
+
#
|
96
|
+
# @param [Enumerable] list
|
97
|
+
# The list to be formatted.
|
98
|
+
#
|
99
|
+
# @return [String]
|
100
|
+
#
|
101
|
+
def format_inclusive_list(list)
|
102
|
+
format_list(list, message('generic.inclusive_conjunction'))
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# Formats a list.
|
108
|
+
#
|
109
|
+
# @see Ward::Errors.format_exclusive_list
|
110
|
+
# @see Ward::Errors.format_inclusive_list
|
111
|
+
#
|
112
|
+
def format_list(list, conjunction)
|
113
|
+
case list.size
|
114
|
+
when 0 then ''
|
115
|
+
when 1 then list.first.to_s
|
116
|
+
when 2 then "#{list.first.to_s} #{conjunction} #{list.last.to_s}"
|
117
|
+
else
|
118
|
+
as_strings = list.map { |value| value.to_s }
|
119
|
+
as_strings[-1] = "#{conjunction} #{as_strings[-1]}"
|
120
|
+
as_strings.join("#{message('generic.list_seperator')} ")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns the en-US error message hash. TEMPORARY.
|
125
|
+
#
|
126
|
+
# @return [Hash]
|
127
|
+
#
|
128
|
+
def messages
|
129
|
+
@error_messages ||= normalise_messages(YAML.load(
|
130
|
+
File.read(File.expand_path('../../../lang/en.yml', __FILE__)) ))
|
131
|
+
end
|
132
|
+
|
133
|
+
# Transforms a hash of messages to a single hash using dot notation.
|
134
|
+
#
|
135
|
+
def normalise_messages(messages, transformed = {}, key_prefix = '')
|
136
|
+
messages.each do |key, value|
|
137
|
+
item_key = key_prefix.empty? ? key : "#{key_prefix}.#{key}"
|
138
|
+
|
139
|
+
if value.is_a?(Hash)
|
140
|
+
normalise_messages(value, transformed, item_key)
|
141
|
+
else
|
142
|
+
transformed[item_key] = value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
transformed
|
147
|
+
end
|
148
|
+
|
149
|
+
end # class << self
|
150
|
+
|
151
|
+
# Creates a new Errors instance.
|
152
|
+
#
|
153
|
+
def initialize
|
154
|
+
@errors = {}
|
155
|
+
end
|
156
|
+
|
157
|
+
# Adds an error message to the instance.
|
158
|
+
#
|
159
|
+
# @param [Symbol, Ward::Context, Ward::ContextChain] attribute
|
160
|
+
# The attribute or context for the error.
|
161
|
+
# @param [String] message
|
162
|
+
# The error message to add.
|
163
|
+
#
|
164
|
+
# @return [String]
|
165
|
+
# Returns the error message which was set.
|
166
|
+
#
|
167
|
+
# @todo
|
168
|
+
# Support symbols for i18n.
|
169
|
+
#
|
170
|
+
def add(attribute, message)
|
171
|
+
if attribute.kind_of?(Context) or attribute.kind_of?(ContextChain)
|
172
|
+
attribute = attribute.attribute
|
173
|
+
end
|
174
|
+
|
175
|
+
@errors[attribute] ||= []
|
176
|
+
@errors[attribute] << message
|
177
|
+
message
|
178
|
+
end
|
179
|
+
|
180
|
+
# Returns an array of the errors present on an an attribute.
|
181
|
+
#
|
182
|
+
# @param [Symbol] attribute
|
183
|
+
# The attribute whose errors you wish to retrieve.
|
184
|
+
#
|
185
|
+
# @return [Array]
|
186
|
+
# Returns the error messages for an attribute, or nil if there are none.
|
187
|
+
#
|
188
|
+
def on(attribute)
|
189
|
+
@errors[attribute]
|
190
|
+
end
|
191
|
+
|
192
|
+
# Iterates through each attribute and the errors.
|
193
|
+
#
|
194
|
+
# @yieldparam [Symbol] attribute
|
195
|
+
# The attribute name.
|
196
|
+
# @yieldparam [Array, nil] messages
|
197
|
+
# An array with each error message for the attribute, or nil if the
|
198
|
+
# attribute has no errors.
|
199
|
+
#
|
200
|
+
def each(&block)
|
201
|
+
@errors.each(&block)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns if there are no errors contained.
|
205
|
+
#
|
206
|
+
# @return [Boolean]
|
207
|
+
#
|
208
|
+
def empty?
|
209
|
+
@errors.empty?
|
210
|
+
end
|
211
|
+
|
212
|
+
end # Errors
|
213
|
+
end # Ward
|