/
allow_value_matcher.rb
182 lines (152 loc) · 5.24 KB
/
allow_value_matcher.rb
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
module Shoulda # :nodoc:
module Matchers
module ActiveModel # :nodoc:
# Ensures that the attribute can be set to the given value or values. If
# multiple values are given the match succeeds only if all given values
# are allowed. Otherwise, the matcher fails at the first bad value in the
# argument list (the remaining arguments are not processed then).
#
# Options:
# * <tt>with_message</tt> - value the test expects to find in
# <tt>errors.on(:attribute)</tt>. Regexp or string. If omitted,
# the test looks for any errors in <tt>errors.on(:attribute)</tt>.
# * <tt>strict</tt> - expects the model to raise an exception when the
# validation fails rather than adding to the errors collection. Used for
# testing `validates!` and the `strict: true` validation options.
#
# Example:
# it { should_not allow_value('bad').for(:isbn) }
# it { should allow_value('isbn 1 2345 6789 0').for(:isbn) }
#
def allow_value(*values)
if values.empty?
raise ArgumentError, 'need at least one argument'
else
AllowValueMatcher.new(*values)
end
end
class AllowValueMatcher # :nodoc:
include Helpers
attr_accessor :attribute_with_message
attr_accessor :options
def initialize(*values)
self.values_to_match = values
self.message_finder_factory = ValidationMessageFinder
self.options = {}
end
def for(attribute)
self.attribute_to_set = attribute
self.attribute_to_check_message_against = attribute
self
end
def on(context)
@context = context
self
end
def with_message(message, options={})
self.options[:expected_message] = message
if options.key?(:against)
self.attribute_to_check_message_against = options[:against]
end
self
end
def strict
self.message_finder_factory = ExceptionMessageFinder
self
end
def matches?(instance)
self.instance = instance
values_to_match.none? do |value|
self.value = value
instance.send("#{attribute_to_set}=", value)
errors_match?
end
end
def failure_message
"Did not expect #{expectation}, got error: #{matched_error}"
end
alias failure_message_for_should failure_message
def failure_message_when_negated
"Expected #{expectation}, got #{error_description}"
end
alias failure_message_for_should_not failure_message_when_negated
def description
message_finder.allow_description(allowed_values)
end
private
attr_accessor :values_to_match, :message_finder_factory,
:instance, :attribute_to_set, :attribute_to_check_message_against,
:context, :value, :matched_error
def errors_match?
has_messages? && errors_for_attribute_match?
end
def has_messages?
message_finder.has_messages?
end
def errors_for_attribute_match?
if expected_message
self.matched_error = errors_match_regexp? || errors_match_string?
else
errors_for_attribute.compact.any?
end
end
def errors_for_attribute
message_finder.messages
end
def errors_match_regexp?
if Regexp === expected_message
errors_for_attribute.detect { |e| e =~ expected_message }
end
end
def errors_match_string?
if errors_for_attribute.include?(expected_message)
expected_message
end
end
def expectation
includes_expected_message = expected_message ? "to include #{expected_message.inspect}" : ''
[error_source, includes_expected_message, "when #{attribute_to_set} is set to #{value.inspect}"].join(' ')
end
def error_source
message_finder.source_description
end
def error_description
message_finder.messages_description
end
def allowed_values
if values_to_match.length > 1
"any of [#{values_to_match.map(&:inspect).join(', ')}]"
else
values_to_match.first.inspect
end
end
def expected_message
if options.key?(:expected_message)
if Symbol === options[:expected_message]
default_expected_message
else
options[:expected_message]
end
end
end
def default_expected_message
message_finder.expected_message_from(default_attribute_message)
end
def default_attribute_message
default_error_message(
options[:expected_message],
model_name: model_name,
instance: instance,
attribute: attribute_to_set
)
end
def model_name
instance.class.to_s.underscore
end
def message_finder
message_finder_factory.new(instance, attribute_to_check_message_against, context)
end
end
end
end
end