ward 0.1.0

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