ward 0.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 (92) hide show
  1. data/.document +5 -0
  2. data/.gitignore +28 -0
  3. data/LICENSE +19 -0
  4. data/README.markdown +99 -0
  5. data/Rakefile +47 -0
  6. data/VERSION +1 -0
  7. data/features/acceptance_matcher.feature +78 -0
  8. data/features/attribute_keyword.feature +13 -0
  9. data/features/close_to_matcher.feature +130 -0
  10. data/features/context_arguments.feature +47 -0
  11. data/features/equal_to_matcher.feature +25 -0
  12. data/features/error_messages.feature +69 -0
  13. data/features/external_validation.feature +15 -0
  14. data/features/has_matcher.feature +72 -0
  15. data/features/has_matcher_initialized_with_expectation.feature +94 -0
  16. data/features/has_matcher_relativities.feature +171 -0
  17. data/features/include_matcher.feature +28 -0
  18. data/features/is_keyword.feature +42 -0
  19. data/features/is_not_keyword.feature +62 -0
  20. data/features/match_matcher.feature +49 -0
  21. data/features/multiple_validators.feature +29 -0
  22. data/features/nil_matcher.feature +25 -0
  23. data/features/predicate_matcher.feature +23 -0
  24. data/features/present_matcher.feature +59 -0
  25. data/features/satisfy_matcher.feature +80 -0
  26. data/features/scenario_validation.feature +81 -0
  27. data/features/step_definitions/external_validation_steps.rb +69 -0
  28. data/features/step_definitions/generic_validation_steps.rb +33 -0
  29. data/features/step_definitions/object_definition_steps.rb +43 -0
  30. data/features/support/env.rb +12 -0
  31. data/features/support/object_builder.rb +33 -0
  32. data/features/support/struct.rb +38 -0
  33. data/lang/en.yml +56 -0
  34. data/lib/ward.rb +26 -0
  35. data/lib/ward/context.rb +70 -0
  36. data/lib/ward/context_chain.rb +87 -0
  37. data/lib/ward/dsl.rb +7 -0
  38. data/lib/ward/dsl/validation_block.rb +73 -0
  39. data/lib/ward/dsl/validation_builder.rb +190 -0
  40. data/lib/ward/errors.rb +213 -0
  41. data/lib/ward/matchers.rb +97 -0
  42. data/lib/ward/matchers/acceptance.rb +43 -0
  43. data/lib/ward/matchers/close_to.rb +60 -0
  44. data/lib/ward/matchers/equal_to.rb +33 -0
  45. data/lib/ward/matchers/has.rb +283 -0
  46. data/lib/ward/matchers/include.rb +54 -0
  47. data/lib/ward/matchers/match.rb +29 -0
  48. data/lib/ward/matchers/matcher.rb +68 -0
  49. data/lib/ward/matchers/nil.rb +30 -0
  50. data/lib/ward/matchers/predicate.rb +31 -0
  51. data/lib/ward/matchers/present.rb +56 -0
  52. data/lib/ward/matchers/satisfy.rb +65 -0
  53. data/lib/ward/spec.rb +17 -0
  54. data/lib/ward/spec/matcher_matcher.rb +114 -0
  55. data/lib/ward/support.rb +7 -0
  56. data/lib/ward/support/basic_object.rb +55 -0
  57. data/lib/ward/support/result.rb +49 -0
  58. data/lib/ward/validator.rb +147 -0
  59. data/lib/ward/validator_set.rb +115 -0
  60. data/lib/ward/version.rb +3 -0
  61. data/spec/lib/has_matcher_relativity_examples.rb +15 -0
  62. data/spec/lib/have_public_method_defined.rb +22 -0
  63. data/spec/rcov.opts +8 -0
  64. data/spec/spec.opts +4 -0
  65. data/spec/spec_helper.rb +19 -0
  66. data/spec/ward/context_chain_spec.rb +178 -0
  67. data/spec/ward/context_spec.rb +57 -0
  68. data/spec/ward/dsl/validation_block_spec.rb +27 -0
  69. data/spec/ward/dsl/validation_builder_spec.rb +212 -0
  70. data/spec/ward/errors_spec.rb +149 -0
  71. data/spec/ward/matchers/acceptance_spec.rb +16 -0
  72. data/spec/ward/matchers/close_to_spec.rb +57 -0
  73. data/spec/ward/matchers/equal_to_spec.rb +16 -0
  74. data/spec/ward/matchers/has_spec.rb +175 -0
  75. data/spec/ward/matchers/include_spec.rb +41 -0
  76. data/spec/ward/matchers/match_spec.rb +21 -0
  77. data/spec/ward/matchers/matcher_spec.rb +54 -0
  78. data/spec/ward/matchers/nil_spec.rb +16 -0
  79. data/spec/ward/matchers/predicate_spec.rb +19 -0
  80. data/spec/ward/matchers/present_spec.rb +16 -0
  81. data/spec/ward/matchers/satisfy_spec.rb +68 -0
  82. data/spec/ward/matchers_spec.rb +51 -0
  83. data/spec/ward/spec/have_public_method_defined_spec.rb +31 -0
  84. data/spec/ward/spec/matcher_matcher_spec.rb +217 -0
  85. data/spec/ward/validator_set_spec.rb +178 -0
  86. data/spec/ward/validator_spec.rb +264 -0
  87. data/tasks/features.rake +15 -0
  88. data/tasks/rcov.rake +24 -0
  89. data/tasks/spec.rake +18 -0
  90. data/tasks/yard.rake +9 -0
  91. data/ward.gemspec +176 -0
  92. metadata +239 -0
@@ -0,0 +1,54 @@
1
+ module Ward
2
+ module Matchers
3
+ # Tests whether the validation value is contained in the expected value.
4
+ #
5
+ # The expected value can be anything which responds to +include?+; if it
6
+ # returns true, the matcher will pass.
7
+ #
8
+ # @example Person role is either :admin or :staff
9
+ #
10
+ # class Person
11
+ # validate do |person|
12
+ # person.role.is.in([:admin, :staff])
13
+ # end
14
+ # end
15
+ #
16
+ class Include < Matcher
17
+
18
+ # Creates a new Include matcher instance.
19
+ #
20
+ # @param [#include?] expected
21
+ # The expected value for the matcher.
22
+ #
23
+ def initialize(expected = nil)
24
+ raise ArgumentError,
25
+ 'The Include matcher requires that a value which responds ' \
26
+ 'to #include? is supplied' unless expected.respond_to?(:include?)
27
+
28
+ super
29
+ end
30
+
31
+ # Returns whether the given value is included in the expected value.
32
+ #
33
+ # @param [#include?] actual
34
+ # The validation value.
35
+ #
36
+ # @return [Boolean]
37
+ #
38
+ def matches?(actual)
39
+ @expected.include?(actual)
40
+ end
41
+
42
+ # Adds extra information to the error message.
43
+ #
44
+ # @param [String] error
45
+ # @return [String]
46
+ #
47
+ def customise_error_values(values)
48
+ values[:expected] = Ward::Errors.format_exclusive_list(@expected)
49
+ values
50
+ end
51
+
52
+ end # Include
53
+ end # Matchers
54
+ end # Ward
@@ -0,0 +1,29 @@
1
+ module Ward
2
+ module Matchers
3
+ # Tests whether the validation value matches the expected value with a
4
+ # regular expression.
5
+ #
6
+ # @example
7
+ #
8
+ # class Person
9
+ # validate do |person|
10
+ # person.name.matches(/^Michael (Scarn|Scott)$/)
11
+ # end
12
+ # end
13
+ #
14
+ class Match < Matcher
15
+
16
+ # Returns whether the given value matches the expected value.
17
+ #
18
+ # @param [#include?] actual
19
+ # The validation value.
20
+ #
21
+ # @return [Boolean]
22
+ #
23
+ def matches?(actual)
24
+ actual.match(@expected)
25
+ end
26
+
27
+ end # Match
28
+ end # Matchers
29
+ end # Ward
@@ -0,0 +1,68 @@
1
+ module Ward
2
+ module Matchers
3
+ # A base class used for creating custom matchers.
4
+ #
5
+ # @abstract
6
+ #
7
+ class Matcher
8
+
9
+ # Returns the expected value.
10
+ #
11
+ # @return [Object]
12
+ #
13
+ attr_reader :expected
14
+
15
+ # Returns any extra arguments given to the matcher.
16
+ #
17
+ # @return [Array]
18
+ #
19
+ attr_reader :extra_args
20
+
21
+ # Creates a new matcher instance.
22
+ #
23
+ # @param [Object] expected
24
+ # The expected value for the matcher.
25
+ #
26
+ def initialize(expected = nil, *extra_args)
27
+ @expected = expected
28
+ @extra_args = extra_args
29
+ end
30
+
31
+ # Returns whether the given value matches the expected value.
32
+ #
33
+ # @param [Object] actual
34
+ # The validation value.
35
+ #
36
+ # @return [Boolean]
37
+ #
38
+ # @abstract
39
+ #
40
+ def matches?(actual)
41
+ true
42
+ end
43
+
44
+ # Allows matcher subclasses to change -- or add values to -- the hash
45
+ # whose values are interpolated with the error string.
46
+ #
47
+ # @param [Hash]
48
+ # The standard values which are interpolated with the error string;
49
+ # contains :context and :expected keys.
50
+ #
51
+ # @return [Hash{Symbol => String}]
52
+ #
53
+ def customise_error_values(values)
54
+ values
55
+ end
56
+
57
+ # Determines the key to be used to find error messages in lang files.
58
+ #
59
+ # @return [String]
60
+ #
61
+ def self.error_id
62
+ @error_id ||= ActiveSupport::Inflector.underscore(
63
+ ActiveSupport::Inflector.demodulize(to_s))
64
+ end
65
+
66
+ end # Matcher
67
+ end # Matchers
68
+ end # Ward
@@ -0,0 +1,30 @@
1
+ module Ward
2
+ module Matchers
3
+ # Tests whether the validation value is nil.
4
+ #
5
+ # @todo Remove once the predicate matcher DSL is available.
6
+ #
7
+ # @example
8
+ #
9
+ # class AverageDogWalker
10
+ # validate do |walker|
11
+ # walker.common_sense.is.nil # Sigh.
12
+ # end
13
+ # end
14
+ #
15
+ class Nil < Matcher
16
+
17
+ # Returns whether the given value is nil.
18
+ #
19
+ # @param [Object] actual
20
+ # The validation value.
21
+ #
22
+ # @return [Boolean]
23
+ #
24
+ def matches?(actual)
25
+ actual.nil?
26
+ end
27
+
28
+ end # Nil
29
+ end # Matchers
30
+ end # Ward
@@ -0,0 +1,31 @@
1
+ module Ward
2
+ module Matchers
3
+ # Tests that a predicate method responds with true.
4
+ #
5
+ # @example Validating that the predicate method responds with true.
6
+ #
7
+ # class Person
8
+ # validate do |person|
9
+ # person.is.important?
10
+ # end
11
+ # end
12
+ #
13
+ class Predicate < Matcher
14
+
15
+ # Returns whether the given value is responds true to the predicate.
16
+ #
17
+ # @param [Object] actual
18
+ # The validation value.
19
+ #
20
+ # @return [Boolean]
21
+ #
22
+ def matches?(actual)
23
+ raise ArgumentError, "#{actual.inspect} does not respond " \
24
+ "to #{expected}" unless actual.respond_to?(@expected)
25
+
26
+ actual.__send__(@expected)
27
+ end
28
+
29
+ end # Predicate
30
+ end # Matchers
31
+ end # Ward
@@ -0,0 +1,56 @@
1
+ module Ward
2
+ module Matchers
3
+ # Tests whether the validation value is present.
4
+ #
5
+ # A "present" value is one which:
6
+ #
7
+ # * is a non-blank string, containing more than just whitespace
8
+ # * responds to #empty? and returns false
9
+ # * does not evaluate to false (i.e. not nil or false)
10
+ #
11
+ # This is equivalent to ActiveSupport's +blank?+ extension methods but
12
+ # note that +blank?+ is not actually used; if you define +blank?+ on a
13
+ # class which is provided to the matcher it will not be called.
14
+ #
15
+ # @todo
16
+ # Once the predicate matcher is available, amend the class documentation
17
+ # to provide an example of how to call +blank?+ explicitly.
18
+ #
19
+ # @example Validating that the name attribute is present
20
+ #
21
+ # class Person
22
+ # validate do |person|
23
+ # person.name.is.present
24
+ # end
25
+ # end
26
+ #
27
+ # @example Validating that the job attribute is not present :(
28
+ #
29
+ # class Person
30
+ # validate do |person|
31
+ # person.job.is_not.present
32
+ # end
33
+ # end
34
+ #
35
+ class Present < Matcher
36
+
37
+ # Returns whether the given value is present.
38
+ #
39
+ # @param [Object] actual
40
+ # The validation value.
41
+ #
42
+ # @return [Boolean]
43
+ #
44
+ def matches?(actual)
45
+ if actual.kind_of?(String)
46
+ actual.match(/\S/)
47
+ elsif actual.respond_to?(:empty?)
48
+ not actual.empty?
49
+ else
50
+ actual
51
+ end
52
+ end
53
+
54
+ end # Present
55
+ end # Matchers
56
+ end # Ward
@@ -0,0 +1,65 @@
1
+ module Ward
2
+ module Matchers
3
+ # Tests whether the validation value satisfies the given block.
4
+ #
5
+ # If the block returns any value other than false, the matcher will assume
6
+ # that the match passed.
7
+ #
8
+ # Alternatively, you may pass a Symbol which identifies a method name; the
9
+ # method will be run with it's return value used to determine if the
10
+ # matcher passed.
11
+ #
12
+ # Adding an explict error message is advised, since the message generated
13
+ # by Ward isn't very helpful: "... is invalid".
14
+ #
15
+ # @example Matching with a block
16
+ #
17
+ # class Record
18
+ # validate do |record|
19
+ # record.name.satisfies do |value, record|
20
+ # value == 'Michael Scarn'
21
+ # end
22
+ # end
23
+ # end
24
+ #
25
+ # @example With a runtime error message
26
+ #
27
+ # class Record
28
+ # validate do |record|
29
+ # record.name.satisfies do |value, record|
30
+ # value == 'Michael Scarn' || [false, "Ooooh noooo"]
31
+ # end
32
+ # end
33
+ # end
34
+ #
35
+ class Satisfy < Matcher
36
+
37
+ # Creates a new matcher instance.
38
+ #
39
+ # @param [Object] expected
40
+ # The expected value for the matcher.
41
+ #
42
+ def initialize(expected = nil, *extra_args, &block)
43
+ super(block, *extra_args)
44
+ end
45
+
46
+ # Returns whether the given value is satisfied by the expected block.
47
+ #
48
+ # @param [Object] actual
49
+ # The validation value.
50
+ # @param [Object] record
51
+ # The full record.
52
+ #
53
+ # @return [Boolean]
54
+ #
55
+ def matches?(actual, record = nil)
56
+ if @expected.arity != 1
57
+ @expected.call(actual, record)
58
+ else
59
+ @expected.call(actual)
60
+ end
61
+ end
62
+
63
+ end # Satisfy
64
+ end # Matchers
65
+ end # Ward
@@ -0,0 +1,17 @@
1
+ #
2
+ # The 'spec' directory contains various reuseable helpers for use with RSpec.
3
+ #
4
+ # The helpers are _not_ loaded when doing +require 'ward'+; if you wish to
5
+ # use these helpers -- perhaps for testing your own custom validations -- you
6
+ # must:
7
+ #
8
+ # require 'ward'
9
+ # require 'ward/spec'
10
+ #
11
+
12
+ require 'ward/spec/matcher_matcher'
13
+
14
+ module Ward
15
+ # Provides helpers and matchers for use in RSpec examples.
16
+ module Spec; end
17
+ end
@@ -0,0 +1,114 @@
1
+ module Ward
2
+ module Spec
3
+ # Since Ward matchers are permitted to return either false, or an Array
4
+ # whose first member is false, to indicate a failure, determining the
5
+ # status of a match attempt relies in you knowing this in advance:
6
+ #
7
+ # result, error = matcher.matches?(value)
8
+ # result.should be_false
9
+ #
10
+ # The helpers within the MatcherMatcher module simplify this situation:
11
+ #
12
+ # matcher.should pass_matcher_with(value)
13
+ # matcher.should fail_matcher_with(value)
14
+ #
15
+ # The +fail_matcher_with+ helper also provides the ability to check the
16
+ # error message returned by the matcher:
17
+ #
18
+ # matcher.should fail_matcher_with(value, :too_short)
19
+ #
20
+ module MatcherMatcher
21
+
22
+ # Formats error messages for spec failures.
23
+ #
24
+ # @param [String, Symbol] result
25
+ # Did you expect a :pass or :fail?
26
+ # @param [Ward::Matchers::Matcher] matcher
27
+ # The matcher instance which was used for the expectation.
28
+ # @param [Object] value
29
+ # The actual value which was supplied to the Ward matcher.
30
+ #
31
+ # @return [String]
32
+ #
33
+ def self.error_message(result, matcher, value)
34
+ status = if result.to_sym == :pass then 'failed' else 'passed' end
35
+
36
+ expected = unless matcher.expected.nil?
37
+ "expected: #{matcher.expected.inspect}, "
38
+ end
39
+
40
+ "expected #{matcher.class.inspect} matcher to #{result}, but it " \
41
+ "#{status} (#{expected}actual: #{value.inspect})"
42
+ end
43
+
44
+ # An RSpec matcher which tests that the Ward matcher passes with a
45
+ # particular value.
46
+ #
47
+ ::Spec::Matchers.define :pass_matcher_with do |value|
48
+ match do |matcher|
49
+ case result = matcher.matches?(value)
50
+ when false, nil then false
51
+ when Array then !! result.first
52
+ else true
53
+ end
54
+ end
55
+
56
+ failure_message_for_should do |matcher|
57
+ Ward::Spec::MatcherMatcher.error_message(:pass, matcher, value)
58
+ end
59
+
60
+ failure_message_for_should_not do |matcher|
61
+ Ward::Spec::MatcherMatcher.error_message(:fail, matcher, value)
62
+ end
63
+ end
64
+
65
+ # An RSpec matcher which tests that the Ward matcher fails with a
66
+ # particular value.
67
+ #
68
+ ::Spec::Matchers.define :fail_matcher_with do |value|
69
+ # Allows further customisation of the matcher, asserting that a
70
+ # particular error message was returned along with the failure. Using
71
+ # this with +should_not+ makes little sense.
72
+ #
73
+ # @param [Symbol, String] expected_error
74
+ #
75
+ def with_error(expected_error)
76
+ @expected_error = expected_error
77
+ self
78
+ end
79
+
80
+ match do |matcher|
81
+ result, error = matcher.matches?(value)
82
+
83
+ if @expected_error and not error.eql?(@expected_error)
84
+ @actual_error = error
85
+ false
86
+ elsif result == false || result.nil?
87
+ true
88
+ else
89
+ false
90
+ end
91
+ end
92
+
93
+ failure_message_for_should do |matcher|
94
+ if @actual_error
95
+ expected = unless matcher.expected.nil?
96
+ "expected: #{matcher.expected.inspect}, "
97
+ end
98
+
99
+ "expected #{matcher.class.inspect} matcher to fail with error " \
100
+ "#{@expected_error.inspect}, but it failed with " \
101
+ "#{@actual_error.inspect} (#{expected}actual: #{value.inspect})"
102
+ else
103
+ Ward::Spec::MatcherMatcher.error_message(:fail, matcher, value)
104
+ end
105
+ end
106
+
107
+ failure_message_for_should_not do |matcher|
108
+ Ward::Spec::MatcherMatcher.error_message(:pass, matcher, value)
109
+ end
110
+ end
111
+
112
+ end # MatcherMatcher
113
+ end # Spec
114
+ end # Ward