shoulda-matchers 3.0.1 → 3.1.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 (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