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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +3 -3
- data/CONTRIBUTING.md +60 -28
- data/Gemfile +1 -0
- data/Gemfile.lock +15 -12
- data/NEWS.md +111 -0
- data/README.md +94 -6
- data/Rakefile +10 -8
- data/custom_plan.rb +88 -0
- data/gemfiles/4.0.0.gemfile +1 -0
- data/gemfiles/4.0.0.gemfile.lock +21 -18
- data/gemfiles/4.0.1.gemfile +1 -0
- data/gemfiles/4.0.1.gemfile.lock +21 -18
- data/gemfiles/4.1.gemfile +1 -0
- data/gemfiles/4.1.gemfile.lock +21 -18
- data/gemfiles/4.2.gemfile +1 -0
- data/gemfiles/4.2.gemfile.lock +24 -21
- data/lib/shoulda/matchers/action_controller/permit_matcher.rb +6 -11
- data/lib/shoulda/matchers/active_model.rb +10 -1
- data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +258 -180
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_changed_value_error.rb +45 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_does_not_exist_error.rb +23 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter.rb +236 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setter_and_validator.rb +62 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters.rb +40 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/attribute_setters_and_validators.rb +48 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_check.rb +14 -0
- data/lib/shoulda/matchers/active_model/allow_value_matcher/successful_setting.rb +14 -0
- data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +34 -14
- data/lib/shoulda/matchers/active_model/helpers.rb +9 -17
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +13 -6
- data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +13 -2
- data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +19 -35
- data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +13 -2
- data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +12 -2
- data/lib/shoulda/matchers/active_model/qualifiers.rb +12 -0
- data/lib/shoulda/matchers/active_model/qualifiers/ignore_interference_by_writer.rb +101 -0
- data/lib/shoulda/matchers/active_model/qualifiers/ignoring_interference_by_writer.rb +21 -0
- data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +30 -32
- data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +5 -8
- data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -22
- data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +27 -16
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +58 -15
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +22 -12
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +165 -87
- data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +7 -9
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +111 -49
- data/lib/shoulda/matchers/active_model/validation_matcher/build_description.rb +60 -0
- data/lib/shoulda/matchers/active_model/validator.rb +71 -52
- data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +19 -5
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +450 -124
- data/lib/shoulda/matchers/util.rb +43 -0
- data/lib/shoulda/matchers/util/word_wrap.rb +59 -31
- data/lib/shoulda/matchers/version.rb +1 -1
- data/script/update_gem_in_all_appraisals +1 -1
- data/script/update_gems_in_all_appraisals +1 -1
- data/spec/acceptance/multiple_libraries_integration_spec.rb +5 -2
- data/spec/acceptance/rails_integration_spec.rb +6 -2
- data/spec/spec_helper.rb +1 -3
- data/spec/support/acceptance/helpers/step_helpers.rb +4 -1
- data/spec/support/tests/current_bundle.rb +21 -7
- data/spec/support/unit/active_record/create_table.rb +54 -0
- data/spec/support/unit/attribute.rb +47 -0
- data/spec/support/unit/capture.rb +6 -0
- data/spec/support/unit/change_value.rb +111 -0
- data/spec/support/unit/create_model_arguments/basic.rb +135 -0
- data/spec/support/unit/create_model_arguments/has_many.rb +15 -0
- data/spec/support/unit/create_model_arguments/uniqueness_matcher.rb +74 -0
- data/spec/support/unit/helpers/active_record_versions.rb +1 -1
- data/spec/support/unit/helpers/class_builder.rb +61 -47
- data/spec/support/unit/helpers/database_helpers.rb +5 -3
- data/spec/support/unit/helpers/model_builder.rb +77 -97
- data/spec/support/unit/helpers/validation_matcher_scenario_helpers.rb +44 -0
- data/spec/support/unit/load_environment.rb +12 -0
- data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +2 -2
- data/spec/support/unit/matchers/fail_with_message_matcher.rb +12 -1
- data/spec/support/unit/model_creation_strategies/active_model.rb +111 -0
- data/spec/support/unit/model_creation_strategies/active_record.rb +77 -0
- data/spec/support/unit/model_creators.rb +19 -0
- data/spec/support/unit/model_creators/active_model.rb +39 -0
- data/spec/support/unit/model_creators/active_record.rb +43 -0
- data/spec/support/unit/model_creators/active_record/has_and_belongs_to_many.rb +95 -0
- data/spec/support/unit/model_creators/active_record/has_many.rb +67 -0
- data/spec/support/unit/model_creators/active_record/uniqueness_matcher.rb +42 -0
- data/spec/support/unit/model_creators/basic.rb +97 -0
- data/spec/support/unit/rails_application.rb +1 -1
- data/spec/support/unit/record_validating_confirmation_builder.rb +3 -7
- data/spec/support/unit/shared_examples/ignoring_interference_by_writer.rb +79 -0
- data/spec/support/unit/validation_matcher_scenario.rb +62 -0
- data/spec/unit/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +4 -0
- data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +575 -140
- data/spec/unit/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +115 -15
- data/spec/unit/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +42 -4
- data/spec/unit/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +92 -6
- data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +122 -10
- data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +306 -58
- data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +122 -3
- data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +805 -131
- data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +196 -29
- data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +82 -40
- data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +600 -101
- data/spec/unit/shoulda/matchers/util/word_wrap_spec.rb +88 -33
- data/spec/unit_spec_helper.rb +10 -22
- data/zeus.json +11 -0
- metadata +64 -23
- data/lib/shoulda/matchers/active_model/strict_validator.rb +0 -51
- data/spec/support/unit/shared_examples/numerical_type_submatcher.rb +0 -15
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +0 -288
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +0 -100
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +0 -100
- data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +0 -100
@@ -7,46 +7,66 @@ module Shoulda
|
|
7
7
|
class DisallowValueMatcher
|
8
8
|
extend Forwardable
|
9
9
|
|
10
|
-
def_delegators
|
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
|
-
|
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
|
-
|
40
|
+
allow_matcher.for(attribute)
|
21
41
|
self
|
22
42
|
end
|
23
43
|
|
24
44
|
def on(context)
|
25
|
-
|
45
|
+
allow_matcher.on(context)
|
26
46
|
self
|
27
47
|
end
|
28
48
|
|
29
49
|
def with_message(message, options={})
|
30
|
-
|
50
|
+
allow_matcher.with_message(message, options)
|
31
51
|
self
|
32
52
|
end
|
33
53
|
|
34
|
-
def
|
35
|
-
|
54
|
+
def strict(strict = true)
|
55
|
+
allow_matcher.strict(strict)
|
36
56
|
self
|
37
57
|
end
|
38
58
|
|
39
|
-
def
|
40
|
-
|
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
|
44
|
-
|
64
|
+
def failure_message
|
65
|
+
allow_matcher.failure_message_when_negated
|
45
66
|
end
|
46
67
|
|
47
|
-
def
|
48
|
-
|
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(
|
7
|
-
|
8
|
-
|
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
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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
|
27
|
-
|
26
|
+
def simple_description
|
27
|
+
description = ''
|
28
28
|
|
29
|
-
if
|
30
|
-
|
29
|
+
if expects_strict?
|
30
|
+
description << ' strictly'
|
31
31
|
end
|
32
32
|
|
33
|
-
|
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
|
10
|
-
|
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
|
12
|
-
:
|
13
|
-
|
14
|
-
|
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
|
29
|
-
|
30
|
-
self
|
30
|
+
def allowed_type_name
|
31
|
+
'number'
|
31
32
|
end
|
32
33
|
|
33
|
-
def
|
34
|
-
|
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
|
10
|
-
|
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
|
10
|
-
'
|
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,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
|