shoulda-matchers 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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