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.
Files changed (43) hide show
  1. data/.travis.yml +5 -0
  2. data/Appraisals +6 -0
  3. data/Gemfile.lock +15 -15
  4. data/NEWS.md +20 -1
  5. data/README.md +6 -1
  6. data/features/step_definitions/rails_steps.rb +3 -3
  7. data/gemfiles/3.0.gemfile.lock +23 -27
  8. data/gemfiles/3.1.gemfile.lock +26 -30
  9. data/gemfiles/3.2.gemfile +16 -0
  10. data/gemfiles/3.2.gemfile.lock +157 -0
  11. data/lib/shoulda/matchers/action_controller/assign_to_matcher.rb +46 -29
  12. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +6 -2
  13. data/lib/shoulda/matchers/action_controller/respond_with_content_type_matcher.rb +12 -7
  14. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +28 -15
  15. data/lib/shoulda/matchers/action_mailer/have_sent_email_matcher.rb +1 -1
  16. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +13 -9
  17. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +9 -8
  18. data/lib/shoulda/matchers/active_model/ensure_exclusion_of_matcher.rb +8 -6
  19. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +63 -8
  20. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +67 -34
  21. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +3 -3
  22. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +9 -5
  23. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +6 -3
  24. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +27 -23
  25. data/lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb +38 -25
  26. data/lib/shoulda/matchers/active_record/association_matcher.rb +49 -33
  27. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +42 -35
  28. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +15 -13
  29. data/lib/shoulda/matchers/active_record/query_the_database_matcher.rb +24 -23
  30. data/lib/shoulda/matchers/active_record/serialize_matcher.rb +13 -12
  31. data/lib/shoulda/matchers/version.rb +1 -1
  32. data/shoulda-matchers.gemspec +1 -1
  33. data/spec/shoulda/action_controller/assign_to_matcher_spec.rb +5 -3
  34. data/spec/shoulda/action_mailer/have_sent_email_spec.rb +40 -0
  35. data/spec/shoulda/active_model/allow_mass_assignment_of_matcher_spec.rb +12 -10
  36. data/spec/shoulda/active_model/ensure_inclusion_of_matcher_spec.rb +52 -0
  37. data/spec/shoulda/active_model/helpers_spec.rb +35 -6
  38. data/spec/shoulda/active_model/validate_presence_of_matcher_spec.rb +0 -1
  39. data/spec/shoulda/active_model/validate_uniqueness_of_matcher_spec.rb +8 -1
  40. data/spec/shoulda/active_record/serialize_matcher_spec.rb +1 -1
  41. data/spec/support/active_model_versions.rb +9 -0
  42. data/spec/support/model_builder.rb +15 -7
  43. 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
- @short_message = message if message
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
- @long_message = message if message
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
- disallows_lower_length &&
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
- :count => @minimum)
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
- :count => @maximum)
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 disallows_lower_length
109
- @minimum == 0 ||
110
- @minimum.nil? ||
111
- disallows_length_of(@minimum - 1, @short_message)
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
- @maximum.nil? || disallows_length_of(@maximum + 1, @long_message)
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
- allows_length_of(@minimum, @short_message)
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
- allows_length_of(@maximum, @long_message)
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
- length.nil? || allows_value_of(string_of_length(length), message)
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
- length.nil? || disallows_value_of(string_of_length(length), message)
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
- @expected_message = message if message
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) && 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 @subject.class.respond_to?(:reflect_on_association) &&
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
- @attribute = attribute
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(', ')}" unless @scopes.blank?
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
- find_existing &&
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 find_existing
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
- unless @scopes.blank?
89
- @scopes.each do |scope|
89
+ if @options[:scopes].present?
90
+ @options[:scopes].all? do |scope|
90
91
  setter = :"#{scope}="
91
- unless @subject.respond_to?(setter)
92
- @failure_message =
93
- "#{class_name} doesn't seem to have a #{scope} attribute."
94
- return false
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
- value.swapcase! if @case_insensitive && value.respond_to?(:swapcase!)
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
- self
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
- description += " allow_destroy => #{@allow_destroy}" if @allow_destroy
64
- description += " limit => #{@limit}" if @limit
65
- description += " update_only => #{@update_only}" if @update_only
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
- if @allow_destroy.nil? || @allow_destroy == config[:allow_destroy]
82
- true
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
- if @limit.nil? || @limit == config[:limit]
92
- true
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
- if @update_only.nil? || @update_only == config[:update_only]
101
- true
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
- @problem = (@update_only ? "should" : "should not") +
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