shoulda-matchers 1.1.0 → 1.2.0
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/.travis.yml +5 -0
- data/Appraisals +6 -0
- data/Gemfile.lock +15 -15
- data/NEWS.md +20 -1
- data/README.md +6 -1
- data/features/step_definitions/rails_steps.rb +3 -3
- data/gemfiles/3.0.gemfile.lock +23 -27
- data/gemfiles/3.1.gemfile.lock +26 -30
- data/gemfiles/3.2.gemfile +16 -0
- data/gemfiles/3.2.gemfile.lock +157 -0
- data/lib/shoulda/matchers/action_controller/assign_to_matcher.rb +46 -29
- data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +6 -2
- data/lib/shoulda/matchers/action_controller/respond_with_content_type_matcher.rb +12 -7
- data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +28 -15
- data/lib/shoulda/matchers/action_mailer/have_sent_email_matcher.rb +1 -1
- data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +13 -9
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +9 -8
- data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +8 -6
- data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +63 -8
- data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +67 -34
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +3 -3
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +9 -5
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +6 -3
- data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +27 -23
- data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +38 -25
- data/lib/shoulda/matchers/active_record/association_matcher.rb +49 -33
- data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +42 -35
- data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +15 -13
- data/lib/shoulda/matchers/active_record/query_the_database_matcher.rb +24 -23
- data/lib/shoulda/matchers/active_record/serialize_matcher.rb +13 -12
- data/lib/shoulda/matchers/version.rb +1 -1
- data/shoulda-matchers.gemspec +1 -1
- data/spec/shoulda/action_controller/assign_to_matcher_spec.rb +5 -3
- data/spec/shoulda/action_mailer/have_sent_email_spec.rb +40 -0
- data/spec/shoulda/active_model/allow_mass_assignment_of_matcher_spec.rb +12 -10
- data/spec/shoulda/active_model/ensure_inclusion_of_matcher_spec.rb +52 -0
- data/spec/shoulda/active_model/helpers_spec.rb +35 -6
- data/spec/shoulda/active_model/validate_presence_of_matcher_spec.rb +0 -1
- data/spec/shoulda/active_model/validate_uniqueness_of_matcher_spec.rb +8 -1
- data/spec/shoulda/active_record/serialize_matcher_spec.rb +1 -1
- data/spec/support/active_model_versions.rb +9 -0
- data/spec/support/model_builder.rb +15 -7
- metadata +123 -128
@@ -2,7 +2,8 @@ module Shoulda # :nodoc:
|
|
2
2
|
module Matchers
|
3
3
|
module ActiveModel # :nodoc:
|
4
4
|
|
5
|
-
# Ensures that the length of the attribute is validated.
|
5
|
+
# Ensures that the length of the attribute is validated. Only works with
|
6
|
+
# string/text columns because it uses a string to check length.
|
6
7
|
#
|
7
8
|
# Options:
|
8
9
|
# * <tt>is_at_least</tt> - minimum length of this attribute
|
@@ -36,47 +37,56 @@ module Shoulda # :nodoc:
|
|
36
37
|
class EnsureLengthOfMatcher < ValidationMatcher # :nodoc:
|
37
38
|
include Helpers
|
38
39
|
|
40
|
+
def initialize(attribute)
|
41
|
+
super(attribute)
|
42
|
+
@options = {}
|
43
|
+
end
|
44
|
+
|
39
45
|
def is_at_least(length)
|
40
|
-
@minimum = length
|
46
|
+
@options[:minimum] = length
|
41
47
|
@short_message ||= :too_short
|
42
48
|
self
|
43
49
|
end
|
44
50
|
|
45
51
|
def is_at_most(length)
|
46
|
-
@maximum = length
|
52
|
+
@options[:maximum] = length
|
47
53
|
@long_message ||= :too_long
|
48
54
|
self
|
49
55
|
end
|
50
56
|
|
51
57
|
def is_equal_to(length)
|
52
|
-
@minimum = length
|
53
|
-
@maximum = length
|
58
|
+
@options[:minimum] = length
|
59
|
+
@options[:maximum] = length
|
54
60
|
@short_message ||= :wrong_length
|
55
61
|
self
|
56
62
|
end
|
57
63
|
|
58
64
|
def with_short_message(message)
|
59
|
-
|
65
|
+
if message
|
66
|
+
@short_message = message
|
67
|
+
end
|
60
68
|
self
|
61
69
|
end
|
62
70
|
alias_method :with_message, :with_short_message
|
63
71
|
|
64
72
|
def with_long_message(message)
|
65
|
-
|
73
|
+
if message
|
74
|
+
@long_message = message
|
75
|
+
end
|
66
76
|
self
|
67
77
|
end
|
68
78
|
|
69
79
|
def description
|
70
80
|
description = "ensure #{@attribute} has a length "
|
71
|
-
if @minimum && @maximum
|
72
|
-
if @minimum == @maximum
|
73
|
-
description << "of exactly #{@minimum}"
|
81
|
+
if @options.key?(:minimum) && @options.key?(:maximum)
|
82
|
+
if @options[:minimum] == @options[:maximum]
|
83
|
+
description << "of exactly #{@options[:minimum]}"
|
74
84
|
else
|
75
|
-
description << "between #{@minimum} and #{@maximum}"
|
85
|
+
description << "between #{@options[:minimum]} and #{@options[:maximum]}"
|
76
86
|
end
|
77
87
|
else
|
78
|
-
description << "of at least #{@minimum}" if @minimum
|
79
|
-
description << "of at most #{@maximum}" if @maximum
|
88
|
+
description << "of at least #{@options[:minimum]}" if @options[:minimum]
|
89
|
+
description << "of at most #{@options[:maximum]}" if @options[:maximum]
|
80
90
|
end
|
81
91
|
description
|
82
92
|
end
|
@@ -84,11 +94,7 @@ module Shoulda # :nodoc:
|
|
84
94
|
def matches?(subject)
|
85
95
|
super(subject)
|
86
96
|
translate_messages!
|
87
|
-
|
88
|
-
allows_minimum_length &&
|
89
|
-
((@minimum == @maximum) ||
|
90
|
-
(disallows_higher_length &&
|
91
|
-
allows_maximum_length))
|
97
|
+
lower_bound_matches? && upper_bound_matches?
|
92
98
|
end
|
93
99
|
|
94
100
|
private
|
@@ -96,39 +102,66 @@ module Shoulda # :nodoc:
|
|
96
102
|
def translate_messages!
|
97
103
|
if Symbol === @short_message
|
98
104
|
@short_message = default_error_message(@short_message,
|
99
|
-
:
|
105
|
+
:model_name => @subject.class.to_s.underscore,
|
106
|
+
:attribute => @attribute,
|
107
|
+
:count => @options[:minimum])
|
100
108
|
end
|
101
109
|
|
102
110
|
if Symbol === @long_message
|
103
111
|
@long_message = default_error_message(@long_message,
|
104
|
-
:
|
112
|
+
:model_name => @subject.class.to_s.underscore,
|
113
|
+
:attribute => @attribute,
|
114
|
+
:count => @options[:maximum])
|
105
115
|
end
|
106
116
|
end
|
107
117
|
|
108
|
-
def
|
109
|
-
|
110
|
-
|
111
|
-
|
118
|
+
def lower_bound_matches?
|
119
|
+
disallows_lower_length? && allows_minimum_length?
|
120
|
+
end
|
121
|
+
|
122
|
+
def upper_bound_matches?
|
123
|
+
disallows_higher_length? && allows_maximum_length?
|
124
|
+
end
|
125
|
+
|
126
|
+
def disallows_lower_length?
|
127
|
+
if @options.key?(:minimum)
|
128
|
+
@options[:minimum] == 0 ||
|
129
|
+
disallows_length_of?(@options[:minimum] - 1, @short_message)
|
130
|
+
else
|
131
|
+
true
|
132
|
+
end
|
112
133
|
end
|
113
134
|
|
114
|
-
def disallows_higher_length
|
115
|
-
@
|
135
|
+
def disallows_higher_length?
|
136
|
+
if @options.key?(:maximum)
|
137
|
+
disallows_length_of?(@options[:maximum] + 1, @long_message)
|
138
|
+
else
|
139
|
+
true
|
140
|
+
end
|
116
141
|
end
|
117
142
|
|
118
|
-
def allows_minimum_length
|
119
|
-
|
143
|
+
def allows_minimum_length?
|
144
|
+
if @options.key?(:minimum)
|
145
|
+
allows_length_of?(@options[:minimum], @short_message)
|
146
|
+
else
|
147
|
+
true
|
148
|
+
end
|
120
149
|
end
|
121
150
|
|
122
|
-
def allows_maximum_length
|
123
|
-
|
151
|
+
def allows_maximum_length?
|
152
|
+
if @options.key?(:maximum)
|
153
|
+
allows_length_of?(@options[:maximum], @long_message)
|
154
|
+
else
|
155
|
+
true
|
156
|
+
end
|
124
157
|
end
|
125
158
|
|
126
|
-
def allows_length_of(length, message)
|
127
|
-
|
159
|
+
def allows_length_of?(length, message)
|
160
|
+
allows_value_of(string_of_length(length), message)
|
128
161
|
end
|
129
162
|
|
130
|
-
def disallows_length_of(length, message)
|
131
|
-
|
163
|
+
def disallows_length_of?(length, message)
|
164
|
+
disallows_value_of(string_of_length(length), message)
|
132
165
|
end
|
133
166
|
|
134
167
|
def string_of_length(length)
|
@@ -20,7 +20,9 @@ module Shoulda # :nodoc:
|
|
20
20
|
class ValidateAcceptanceOfMatcher < ValidationMatcher # :nodoc:
|
21
21
|
|
22
22
|
def with_message(message)
|
23
|
-
|
23
|
+
if message
|
24
|
+
@expected_message = message
|
25
|
+
end
|
24
26
|
self
|
25
27
|
end
|
26
28
|
|
@@ -33,9 +35,7 @@ module Shoulda # :nodoc:
|
|
33
35
|
def description
|
34
36
|
"require #{@attribute} to be accepted"
|
35
37
|
end
|
36
|
-
|
37
38
|
end
|
38
|
-
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Shoulda # :nodoc:
|
2
2
|
module Matchers
|
3
3
|
module ActiveModel # :nodoc:
|
4
|
-
# Ensure that the attribute is numeric
|
4
|
+
# Ensure that the attribute is numeric.
|
5
5
|
#
|
6
6
|
# Options:
|
7
7
|
# * <tt>with_message</tt> - value the test expects to find in
|
@@ -18,9 +18,13 @@ module Shoulda # :nodoc:
|
|
18
18
|
end
|
19
19
|
|
20
20
|
class ValidateNumericalityOfMatcher < ValidationMatcher # :nodoc:
|
21
|
+
def initialize(attribute)
|
22
|
+
super(attribute)
|
23
|
+
@options = {}
|
24
|
+
end
|
21
25
|
|
22
26
|
def only_integer
|
23
|
-
@only_integer = true
|
27
|
+
@options[:only_integer] = true
|
24
28
|
self
|
25
29
|
end
|
26
30
|
|
@@ -43,7 +47,7 @@ module Shoulda # :nodoc:
|
|
43
47
|
private
|
44
48
|
|
45
49
|
def allowed_type
|
46
|
-
if @only_integer
|
50
|
+
if @options[:only_integer]
|
47
51
|
"integer"
|
48
52
|
else
|
49
53
|
"numeric"
|
@@ -51,9 +55,9 @@ module Shoulda # :nodoc:
|
|
51
55
|
end
|
52
56
|
|
53
57
|
def disallows_non_integers?
|
54
|
-
if @only_integer
|
58
|
+
if @options[:only_integer]
|
55
59
|
message = @expected_message || :not_an_integer
|
56
|
-
disallows_value_of(0.1, message)
|
60
|
+
disallows_value_of(0.1, message)
|
57
61
|
else
|
58
62
|
true
|
59
63
|
end
|
@@ -47,15 +47,18 @@ module Shoulda # :nodoc:
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def collection?
|
50
|
-
if
|
51
|
-
reflection = @subject.class.reflect_on_association(@attribute)
|
50
|
+
if reflection
|
52
51
|
[:has_many, :has_and_belongs_to_many].include?(reflection.macro)
|
53
52
|
else
|
54
53
|
false
|
55
54
|
end
|
56
55
|
end
|
57
|
-
end
|
58
56
|
|
57
|
+
def reflection
|
58
|
+
@subject.class.respond_to?(:reflect_on_association) &&
|
59
|
+
@subject.class.reflect_on_association(@attribute)
|
60
|
+
end
|
61
|
+
end
|
59
62
|
end
|
60
63
|
end
|
61
64
|
end
|
@@ -38,11 +38,12 @@ module Shoulda # :nodoc:
|
|
38
38
|
include Helpers
|
39
39
|
|
40
40
|
def initialize(attribute)
|
41
|
-
|
41
|
+
super(attribute)
|
42
|
+
@options = {}
|
42
43
|
end
|
43
44
|
|
44
45
|
def scoped_to(*scopes)
|
45
|
-
@scopes = [*scopes].flatten
|
46
|
+
@options[:scopes] = [*scopes].flatten
|
46
47
|
self
|
47
48
|
end
|
48
49
|
|
@@ -52,30 +53,30 @@ module Shoulda # :nodoc:
|
|
52
53
|
end
|
53
54
|
|
54
55
|
def case_insensitive
|
55
|
-
@case_insensitive = true
|
56
|
+
@options[:case_insensitive] = true
|
56
57
|
self
|
57
58
|
end
|
58
59
|
|
59
60
|
def description
|
60
61
|
result = "require "
|
61
|
-
result << "case sensitive " unless @case_insensitive
|
62
|
+
result << "case sensitive " unless @options[:case_insensitive]
|
62
63
|
result << "unique value for #{@attribute}"
|
63
|
-
result << " scoped to #{@scopes.join(', ')}"
|
64
|
+
result << " scoped to #{@options[:scopes].join(', ')}" if @options[:scopes].present?
|
64
65
|
result
|
65
66
|
end
|
66
67
|
|
67
68
|
def matches?(subject)
|
68
69
|
@subject = subject.class.new
|
69
70
|
@expected_message ||= :taken
|
70
|
-
|
71
|
+
has_existing? &&
|
71
72
|
set_scoped_attributes &&
|
72
|
-
validate_attribute &&
|
73
|
-
validate_after_scope_change
|
73
|
+
validate_attribute? &&
|
74
|
+
validate_after_scope_change?
|
74
75
|
end
|
75
76
|
|
76
77
|
private
|
77
78
|
|
78
|
-
def
|
79
|
+
def has_existing?
|
79
80
|
if @existing = @subject.class.find(:first)
|
80
81
|
true
|
81
82
|
else
|
@@ -85,32 +86,34 @@ module Shoulda # :nodoc:
|
|
85
86
|
end
|
86
87
|
|
87
88
|
def set_scoped_attributes
|
88
|
-
|
89
|
-
@scopes.
|
89
|
+
if @options[:scopes].present?
|
90
|
+
@options[:scopes].all? do |scope|
|
90
91
|
setter = :"#{scope}="
|
91
|
-
|
92
|
-
@
|
93
|
-
|
94
|
-
|
92
|
+
if @subject.respond_to?(setter)
|
93
|
+
@subject.send("#{scope}=", @existing.send(scope))
|
94
|
+
true
|
95
|
+
else
|
96
|
+
@failure_message = "#{class_name} doesn't seem to have a #{scope} attribute."
|
97
|
+
false
|
95
98
|
end
|
96
|
-
@subject.send("#{scope}=", @existing.send(scope))
|
97
99
|
end
|
100
|
+
else
|
101
|
+
true
|
98
102
|
end
|
99
|
-
true
|
100
103
|
end
|
101
104
|
|
102
|
-
def validate_attribute
|
105
|
+
def validate_attribute?
|
103
106
|
disallows_value_of(existing_value, @expected_message)
|
104
107
|
end
|
105
108
|
|
106
109
|
# TODO: There is a chance that we could change the scoped field
|
107
110
|
# to a value that's already taken. An alternative implementation
|
108
111
|
# could actually find all values for scope and create a unique
|
109
|
-
def validate_after_scope_change
|
110
|
-
if @scopes.blank?
|
112
|
+
def validate_after_scope_change?
|
113
|
+
if @options[:scopes].blank?
|
111
114
|
true
|
112
115
|
else
|
113
|
-
@scopes.all? do |scope|
|
116
|
+
@options[:scopes].all? do |scope|
|
114
117
|
previous_value = @existing.send(scope)
|
115
118
|
|
116
119
|
# Assume the scope is a foreign key if the field is nil
|
@@ -142,11 +145,12 @@ module Shoulda # :nodoc:
|
|
142
145
|
|
143
146
|
def existing_value
|
144
147
|
value = @existing.send(@attribute)
|
145
|
-
|
148
|
+
if @options[:case_insensitive] && value.respond_to?(:swapcase!)
|
149
|
+
value.swapcase!
|
150
|
+
end
|
146
151
|
value
|
147
152
|
end
|
148
153
|
end
|
149
|
-
|
150
154
|
end
|
151
155
|
end
|
152
156
|
end
|
@@ -24,21 +24,21 @@ module Shoulda
|
|
24
24
|
class AcceptNestedAttributesForMatcher
|
25
25
|
def initialize(name)
|
26
26
|
@name = name
|
27
|
-
|
27
|
+
@options = {}
|
28
28
|
end
|
29
29
|
|
30
30
|
def allow_destroy(allow_destroy)
|
31
|
-
@allow_destroy = allow_destroy
|
31
|
+
@options[:allow_destroy] = allow_destroy
|
32
32
|
self
|
33
33
|
end
|
34
34
|
|
35
35
|
def limit(limit)
|
36
|
-
@limit = limit
|
36
|
+
@options[:limit] = limit
|
37
37
|
self
|
38
38
|
end
|
39
39
|
|
40
40
|
def update_only(update_only)
|
41
|
-
@update_only = update_only
|
41
|
+
@options[:update_only] = update_only
|
42
42
|
self
|
43
43
|
end
|
44
44
|
|
@@ -60,9 +60,15 @@ module Shoulda
|
|
60
60
|
|
61
61
|
def description
|
62
62
|
description = "accepts_nested_attributes_for :#{@name}"
|
63
|
-
|
64
|
-
|
65
|
-
|
63
|
+
if @options.key?(:allow_destroy)
|
64
|
+
description += " allow_destroy => #{@options[:allow_destroy]}"
|
65
|
+
end
|
66
|
+
if @options.key?(:limit)
|
67
|
+
description += " limit => #{@options[:limit]}"
|
68
|
+
end
|
69
|
+
if @options.key?(:update_only)
|
70
|
+
description += " update_only => #{@options[:update_only]}"
|
71
|
+
end
|
66
72
|
description
|
67
73
|
end
|
68
74
|
|
@@ -78,31 +84,30 @@ module Shoulda
|
|
78
84
|
end
|
79
85
|
|
80
86
|
def allow_destroy_correct?
|
81
|
-
|
82
|
-
|
83
|
-
else
|
84
|
-
@problem = (@allow_destroy ? "should" : "should not") +
|
85
|
-
" allow destroy"
|
86
|
-
false
|
87
|
-
end
|
87
|
+
failure_message = "#{should_or_should_not(@options[:allow_destroy])} allow destroy"
|
88
|
+
verify_option_is_correct(:allow_destroy, failure_message)
|
88
89
|
end
|
89
90
|
|
90
91
|
def limit_correct?
|
91
|
-
|
92
|
-
|
93
|
-
else
|
94
|
-
@problem = "limit should be #@limit, got #{config[:limit]}"
|
95
|
-
false
|
96
|
-
end
|
92
|
+
failure_message = "limit should be #{@options[:limit]}, got #{config[:limit]}"
|
93
|
+
verify_option_is_correct(:limit, failure_message)
|
97
94
|
end
|
98
95
|
|
99
96
|
def update_only_correct?
|
100
|
-
|
101
|
-
|
97
|
+
failure_message = "#{should_or_should_not(@options[:update_only])} be update only"
|
98
|
+
verify_option_is_correct(:update_only, failure_message)
|
99
|
+
end
|
100
|
+
|
101
|
+
def verify_option_is_correct(option, failure_message)
|
102
|
+
if @options.key?(option)
|
103
|
+
if @options[option] == config[option]
|
104
|
+
true
|
105
|
+
else
|
106
|
+
@problem = failure_message
|
107
|
+
false
|
108
|
+
end
|
102
109
|
else
|
103
|
-
|
104
|
-
" be update only"
|
105
|
-
false
|
110
|
+
true
|
106
111
|
end
|
107
112
|
end
|
108
113
|
|
@@ -121,6 +126,14 @@ module Shoulda
|
|
121
126
|
def expectation
|
122
127
|
"#{model_class.name} to accept nested attributes for #{@name}"
|
123
128
|
end
|
129
|
+
|
130
|
+
def should_or_should_not(value)
|
131
|
+
if value
|
132
|
+
"should"
|
133
|
+
else
|
134
|
+
"should not"
|
135
|
+
end
|
136
|
+
end
|
124
137
|
end
|
125
138
|
end
|
126
139
|
end
|