shoulda-matchers 3.0.1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +3 -3
  4. data/CONTRIBUTING.md +60 -28
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +15 -12
  7. data/NEWS.md +111 -0
  8. data/README.md +94 -6
  9. data/Rakefile +10 -8
  10. data/custom_plan.rb +88 -0
  11. data/gemfiles/4.0.0.gemfile +1 -0
  12. data/gemfiles/4.0.0.gemfile.lock +21 -18
  13. data/gemfiles/4.0.1.gemfile +1 -0
  14. data/gemfiles/4.0.1.gemfile.lock +21 -18
  15. data/gemfiles/4.1.gemfile +1 -0
  16. data/gemfiles/4.1.gemfile.lock +21 -18
  17. data/gemfiles/4.2.gemfile +1 -0
  18. data/gemfiles/4.2.gemfile.lock +24 -21
  19. data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -11
  20. data/lib/shoulda/matchers/active_model.rb +10 -1
  21. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +258 -180
  22. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
  23. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
  24. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +236 -0
  25. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
  26. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
  27. data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
  28. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
  29. data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
  30. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +34 -14
  31. data/lib/shoulda/matchers/active_model/helpers.rb +9 -17
  32. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -6
  33. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +13 -2
  34. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +19 -35
  35. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +13 -2
  36. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +12 -2
  37. data/lib/shoulda/matchers/active_model/qualifiers.rb +12 -0
  38. data/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
  39. data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
  40. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -32
  41. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -8
  42. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -22
  43. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +27 -16
  44. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +58 -15
  45. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +22 -12
  46. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +165 -87
  47. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +7 -9
  48. data/lib/shoulda/matchers/active_model/validation_matcher.rb +111 -49
  49. data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +60 -0
  50. data/lib/shoulda/matchers/active_model/validator.rb +71 -52
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +19 -5
  52. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +450 -124
  53. data/lib/shoulda/matchers/util.rb +43 -0
  54. data/lib/shoulda/matchers/util/word_wrap.rb +59 -31
  55. data/lib/shoulda/matchers/version.rb +1 -1
  56. data/script/update_gem_in_all_appraisals +1 -1
  57. data/script/update_gems_in_all_appraisals +1 -1
  58. data/spec/acceptance/multiple_libraries_integration_spec.rb +5 -2
  59. data/spec/acceptance/rails_integration_spec.rb +6 -2
  60. data/spec/spec_helper.rb +1 -3
  61. data/spec/support/acceptance/helpers/step_helpers.rb +4 -1
  62. data/spec/support/tests/current_bundle.rb +21 -7
  63. data/spec/support/unit/active_record/create_table.rb +54 -0
  64. data/spec/support/unit/attribute.rb +47 -0
  65. data/spec/support/unit/capture.rb +6 -0
  66. data/spec/support/unit/change_value.rb +111 -0
  67. data/spec/support/unit/create_model_arguments/basic.rb +135 -0
  68. data/spec/support/unit/create_model_arguments/has_many.rb +15 -0
  69. data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +74 -0
  70. data/spec/support/unit/helpers/active_record_versions.rb +1 -1
  71. data/spec/support/unit/helpers/class_builder.rb +61 -47
  72. data/spec/support/unit/helpers/database_helpers.rb +5 -3
  73. data/spec/support/unit/helpers/model_builder.rb +77 -97
  74. data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +44 -0
  75. data/spec/support/unit/load_environment.rb +12 -0
  76. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +2 -2
  77. data/spec/support/unit/matchers/fail_with_message_matcher.rb +12 -1
  78. data/spec/support/unit/model_creation_strategies/active_model.rb +111 -0
  79. data/spec/support/unit/model_creation_strategies/active_record.rb +77 -0
  80. data/spec/support/unit/model_creators.rb +19 -0
  81. data/spec/support/unit/model_creators/active_model.rb +39 -0
  82. data/spec/support/unit/model_creators/active_record.rb +43 -0
  83. data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +95 -0
  84. data/spec/support/unit/model_creators/active_record/has_many.rb +67 -0
  85. data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +42 -0
  86. data/spec/support/unit/model_creators/basic.rb +97 -0
  87. data/spec/support/unit/rails_application.rb +1 -1
  88. data/spec/support/unit/record_validating_confirmation_builder.rb +3 -7
  89. data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +79 -0
  90. data/spec/support/unit/validation_matcher_scenario.rb +62 -0
  91. data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +4 -0
  92. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +575 -140
  93. data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +115 -15
  94. data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +42 -4
  95. data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +92 -6
  96. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +122 -10
  97. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +306 -58
  98. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +122 -3
  99. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +805 -131
  100. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +196 -29
  101. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +82 -40
  102. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +600 -101
  103. data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +88 -33
  104. data/spec/unit_spec_helper.rb +10 -22
  105. data/zeus.json +11 -0
  106. metadata +64 -23
  107. data/lib/shoulda/matchers/active_model/strict_validator.rb +0 -51
  108. data/spec/support/unit/shared_examples/numerical_type_submatcher.rb +0 -15
  109. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +0 -288
  110. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +0 -100
  111. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +0 -100
  112. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +0 -100
@@ -0,0 +1,14 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ class AllowValueMatcher
5
+ # @private
6
+ class SuccessfulCheck
7
+ def successful?
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ class AllowValueMatcher
5
+ # @private
6
+ class SuccessfulSetting
7
+ def successful?
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -7,46 +7,66 @@ module Shoulda
7
7
  class DisallowValueMatcher
8
8
  extend Forwardable
9
9
 
10
- def_delegators :allow_matcher, :_after_setting_value
10
+ def_delegators(
11
+ :allow_matcher,
12
+ :_after_setting_value,
13
+ :attribute_changed_value_message=,
14
+ :attribute_to_set,
15
+ :description,
16
+ :expects_strict?,
17
+ :failure_message_preface,
18
+ :failure_message_preface=,
19
+ :ignore_interference_by_writer,
20
+ :last_attribute_setter_used,
21
+ :last_value_set,
22
+ :model,
23
+ :simple_description,
24
+ :values_to_preset=,
25
+ )
26
+
11
27
  def initialize(value)
12
28
  @allow_matcher = AllowValueMatcher.new(value)
13
29
  end
14
30
 
15
31
  def matches?(subject)
16
- !@allow_matcher.matches?(subject)
32
+ allow_matcher.does_not_match?(subject)
33
+ end
34
+
35
+ def does_not_match?(subject)
36
+ allow_matcher.matches?(subject)
17
37
  end
18
38
 
19
39
  def for(attribute)
20
- @allow_matcher.for(attribute)
40
+ allow_matcher.for(attribute)
21
41
  self
22
42
  end
23
43
 
24
44
  def on(context)
25
- @allow_matcher.on(context)
45
+ allow_matcher.on(context)
26
46
  self
27
47
  end
28
48
 
29
49
  def with_message(message, options={})
30
- @allow_matcher.with_message(message, options)
50
+ allow_matcher.with_message(message, options)
31
51
  self
32
52
  end
33
53
 
34
- def ignoring_interference_by_writer
35
- @allow_matcher.ignoring_interference_by_writer
54
+ def strict(strict = true)
55
+ allow_matcher.strict(strict)
36
56
  self
37
57
  end
38
58
 
39
- def failure_message
40
- @allow_matcher.failure_message_when_negated
59
+ def ignoring_interference_by_writer(value = :always)
60
+ allow_matcher.ignoring_interference_by_writer(value)
61
+ self
41
62
  end
42
63
 
43
- def failure_message_when_negated
44
- @allow_matcher.failure_message
64
+ def failure_message
65
+ allow_matcher.failure_message_when_negated
45
66
  end
46
67
 
47
- def strict
48
- @allow_matcher.strict
49
- self
68
+ def failure_message_when_negated
69
+ allow_matcher.failure_message
50
70
  end
51
71
 
52
72
  protected
@@ -3,25 +3,17 @@ module Shoulda
3
3
  module ActiveModel
4
4
  # @private
5
5
  module Helpers
6
- def pretty_error_messages(obj)
7
- obj.errors.map do |attribute, message|
8
- full_message = message.dup.inspect
9
- parenthetical_parts = []
10
-
11
- unless attribute.to_sym == :base
12
- parenthetical_parts << "attribute: #{attribute}"
13
-
14
- if obj.respond_to?(attribute)
15
- parenthetical_parts << "value: #{obj.__send__(attribute).inspect}"
16
- end
17
- end
6
+ def pretty_error_messages(object)
7
+ format_validation_errors(object.errors)
8
+ end
18
9
 
19
- if parenthetical_parts.any?
20
- full_message << " (#{parenthetical_parts.join(', ')})"
21
- end
10
+ def format_validation_errors(errors)
11
+ list_items = errors.keys.map do |attribute|
12
+ messages = errors[attribute]
13
+ "* #{attribute}: #{messages}"
14
+ end
22
15
 
23
- "* " + full_message
24
- end.join("\n")
16
+ list_items.join("\n")
25
17
  end
26
18
 
27
19
  def default_error_message(type, options = {})
@@ -13,6 +13,7 @@ module Shoulda
13
13
  }
14
14
 
15
15
  def initialize(numericality_matcher, value, operator)
16
+ super(nil)
16
17
  unless numericality_matcher.respond_to? :diff_to_compare
17
18
  raise ArgumentError, 'numericality_matcher is invalid'
18
19
  end
@@ -20,17 +21,18 @@ module Shoulda
20
21
  @value = value
21
22
  @operator = operator
22
23
  @message = ERROR_MESSAGES[operator]
23
- @strict = false
24
24
  end
25
25
 
26
- def description
27
- message = "validate that #{@attribute} is #{comparison_expectation} #{@value}"
26
+ def simple_description
27
+ description = ''
28
28
 
29
- if @strict
30
- message << " strictly"
29
+ if expects_strict?
30
+ description << ' strictly'
31
31
  end
32
32
 
33
- message
33
+ description +
34
+ "disallow :#{attribute} from being a number that is not " +
35
+ "#{comparison_expectation} #{@value}"
34
36
  end
35
37
 
36
38
  def for(attribute)
@@ -39,10 +41,15 @@ module Shoulda
39
41
  end
40
42
 
41
43
  def with_message(message)
44
+ @expects_custom_validation_message = true
42
45
  @message = message
43
46
  self
44
47
  end
45
48
 
49
+ def expects_custom_validation_message?
50
+ @expects_custom_validation_message
51
+ end
52
+
46
53
  def matches?(subject)
47
54
  @subject = subject
48
55
  all_bounds_correct?
@@ -6,8 +6,19 @@ module Shoulda
6
6
  class EvenNumberMatcher < NumericTypeMatcher
7
7
  NON_EVEN_NUMBER_VALUE = 1
8
8
 
9
- def allowed_type
10
- 'even numbers'
9
+ def simple_description
10
+ description = ''
11
+
12
+ if expects_strict?
13
+ description << 'strictly '
14
+ end
15
+
16
+ description +
17
+ "disallow :#{attribute} from being an odd number"
18
+ end
19
+
20
+ def allowed_type_adjective
21
+ 'even'
11
22
  end
12
23
 
13
24
  def diff_to_compare
@@ -8,35 +8,31 @@ module Shoulda
8
8
  class NumericTypeMatcher
9
9
  extend Forwardable
10
10
 
11
- def_delegators :disallow_value_matcher, :matches?, :failure_message,
12
- :failure_message_when_negated
13
-
14
- def initialize(numeric_type_matcher, attribute, options = {})
11
+ def_delegators(
12
+ :disallow_value_matcher,
13
+ :expects_custom_validation_message?,
14
+ :expects_strict?,
15
+ :failure_message,
16
+ :failure_message_when_negated,
17
+ :ignore_interference_by_writer,
18
+ :ignoring_interference_by_writer,
19
+ :matches?,
20
+ :on,
21
+ :strict,
22
+ :with_message,
23
+ )
24
+
25
+ def initialize(numeric_type_matcher, attribute)
15
26
  @numeric_type_matcher = numeric_type_matcher
16
27
  @attribute = attribute
17
- @options = options
18
- @message = nil
19
- @context = nil
20
- @strict = false
21
- end
22
-
23
- def with_message(message)
24
- @message = message
25
- self
26
28
  end
27
29
 
28
- def strict
29
- @strict = true
30
- self
30
+ def allowed_type_name
31
+ 'number'
31
32
  end
32
33
 
33
- def on(context)
34
- @context = context
35
- self
36
- end
37
-
38
- def allowed_type
39
- raise NotImplementedError
34
+ def allowed_type_adjective
35
+ ''
40
36
  end
41
37
 
42
38
  def diff_to_compare
@@ -62,18 +58,6 @@ module Shoulda
62
58
  DisallowValueMatcher.new(disallowed_value).tap do |matcher|
63
59
  matcher.for(attribute)
64
60
  wrap_disallow_value_matcher(matcher)
65
-
66
- if @message
67
- matcher.with_message(@message)
68
- end
69
-
70
- if @strict
71
- matcher.strict
72
- end
73
-
74
- if @context
75
- matcher.on(@context)
76
- end
77
61
  end
78
62
  end
79
63
  end
@@ -6,8 +6,19 @@ module Shoulda
6
6
  class OddNumberMatcher < NumericTypeMatcher
7
7
  NON_ODD_NUMBER_VALUE = 2
8
8
 
9
- def allowed_type
10
- 'odd numbers'
9
+ def simple_description
10
+ description = ''
11
+
12
+ if expects_strict?
13
+ description << 'strictly '
14
+ end
15
+
16
+ description +
17
+ "disallow :#{attribute} from being an even number"
18
+ end
19
+
20
+ def allowed_type_adjective
21
+ 'odd'
11
22
  end
12
23
 
13
24
  def diff_to_compare
@@ -6,8 +6,18 @@ module Shoulda
6
6
  class OnlyIntegerMatcher < NumericTypeMatcher
7
7
  NON_INTEGER_VALUE = 0.1
8
8
 
9
- def allowed_type
10
- 'integers'
9
+ def simple_description
10
+ description = ''
11
+
12
+ if expects_strict?
13
+ description << ' strictly'
14
+ end
15
+
16
+ description + "disallow :#{attribute} from being a decimal number"
17
+ end
18
+
19
+ def allowed_type_name
20
+ 'integer'
11
21
  end
12
22
 
13
23
  def diff_to_compare
@@ -0,0 +1,12 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ # @private
5
+ module Qualifiers
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ require_relative 'qualifiers/ignore_interference_by_writer'
12
+ require_relative 'qualifiers/ignoring_interference_by_writer'
@@ -0,0 +1,101 @@
1
+ module Shoulda
2
+ module Matchers
3
+ module ActiveModel
4
+ module Qualifiers
5
+ # @private
6
+ class IgnoreInterferenceByWriter
7
+ attr_reader :setting, :condition
8
+
9
+ def initialize(argument = :always)
10
+ set(argument)
11
+ @changed = false
12
+ end
13
+
14
+ def set(argument)
15
+ if argument.is_a?(self.class)
16
+ @setting = argument.setting
17
+ @condition = argument.condition
18
+ else
19
+ case argument
20
+ when true, :always
21
+ @setting = :always
22
+ when false, :never
23
+ @setting = :never
24
+ else
25
+ @setting = :sometimes
26
+
27
+ if argument.is_a?(Hash)
28
+ @condition = argument.fetch(:when)
29
+ else
30
+ raise invalid_argument_error(argument)
31
+ end
32
+ end
33
+ end
34
+
35
+ @changed = true
36
+
37
+ self
38
+ rescue KeyError
39
+ raise invalid_argument_error(argument)
40
+ end
41
+
42
+ def default_to(argument)
43
+ temporary_ignore_interference_by_writer =
44
+ IgnoreInterferenceByWriter.new(argument)
45
+
46
+ unless changed?
47
+ @setting = temporary_ignore_interference_by_writer.setting
48
+ @condition = temporary_ignore_interference_by_writer.condition
49
+ end
50
+
51
+ self
52
+ end
53
+
54
+ def considering?(value)
55
+ case setting
56
+ when :always then true
57
+ when :never then false
58
+ else condition_matches?(value)
59
+ end
60
+ end
61
+
62
+ def always?
63
+ setting == :always
64
+ end
65
+
66
+ def never?
67
+ setting == :never
68
+ end
69
+
70
+ def changed?
71
+ @changed
72
+ end
73
+
74
+ private
75
+
76
+ def invalid_argument_error(invalid_argument)
77
+ ArgumentError.new(<<-ERROR)
78
+ Unknown argument: #{invalid_argument.inspect}.
79
+
80
+ ignoring_interference_by_writer takes one of three arguments:
81
+
82
+ * A symbol, either :never or :always.
83
+ * A boolean, either true (which means always) or false (which means
84
+ never).
85
+ * A hash with a single key, :when, and a single value, which is either
86
+ the name of a method or a Proc.
87
+ ERROR
88
+ end
89
+
90
+ def condition_matches?(value)
91
+ if condition.respond_to?(:call)
92
+ condition.call(value)
93
+ else
94
+ value.public_send(condition)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end