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,7 @@
1
+ require 'ward/support/basic_object'
2
+ require 'ward/support/result'
3
+
4
+ module Ward
5
+ # Support classes.
6
+ module Support; end
7
+ end # Ward
@@ -0,0 +1,55 @@
1
+ module Ward
2
+ module Support
3
+ # Used by the DSL classes. Provides a BasicObject implementation for
4
+ # Ruby versions older than 1.9.
5
+ #
6
+ # From Sequel, with modifications to support RSpec.
7
+ #
8
+ class BasicObject
9
+
10
+ # Lookup missing constants in ::Object
11
+ def self.const_missing(name)
12
+ ::Object.const_get(name)
13
+ end
14
+
15
+ if RUBY_VERSION < '1.9.0'
16
+ # If on Ruby 1.8, create a Ward::BasicObject class that is similar to
17
+ # the Ruby 1.9 BasicObject class. This is used as the basis for the
18
+ # DSL classes.
19
+ (instance_methods - %w( __id__ __send__ instance_eval == equal?
20
+ should should_not )).each do |method|
21
+ undef_method(method.to_sym)
22
+ end
23
+ end
24
+
25
+ end # BasicObject
26
+ end # Support
27
+ end # Ward
28
+
29
+ #
30
+ # Sequel License:
31
+ #
32
+ # Copyright (c) 2007-2008 Sharon Rosner
33
+ # Copyright (c) 2008-2010 Jeremy Evans
34
+ #
35
+ # Permission is hereby granted, free of charge, to any person
36
+ # obtaining a copy of this software and associated documentation
37
+ # files (the "Software"), to deal in the Software without
38
+ # restriction, including without limitation the rights to use,
39
+ # copy, modify, merge, publish, distribute, sublicense, and/or
40
+ # sell copies of the Software, and to permit persons to whom the
41
+ # Software is furnished to do so, subject to the following
42
+ # conditions:
43
+ #
44
+ # The above copyright notice and this permission notice shall be
45
+ # included in all copies or substantial portions of the Software.
46
+ #
47
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
48
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
49
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
50
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
51
+ # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
52
+ # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
53
+ # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
54
+ # THE SOFTWARE.
55
+ #
@@ -0,0 +1,49 @@
1
+ module Ward
2
+ module Support
3
+ # Used when validating objects which don't include the Validation
4
+ # module. Contains the results of running a ValidatorSet against a record,
5
+ # and any errors which occurred.
6
+ #
7
+ #
8
+ class Result
9
+
10
+ # Returns the errors which were found when validating.
11
+ #
12
+ # An Errors instance will always be returned, even if there were no
13
+ # errors with the record.
14
+ #
15
+ # @return [Ward:Errors]
16
+ #
17
+ attr_reader :errors
18
+
19
+ # Creates a new Result instance.
20
+ #
21
+ # @param [Ward::Errors] errors
22
+ # The errors which were found by the validations.
23
+ #
24
+ def initialize(errors)
25
+ @errors = errors
26
+ @result = errors.empty?
27
+ end
28
+
29
+ # Returns true if the validations passed.
30
+ #
31
+ # @return [Boolean]
32
+ #
33
+ def pass?
34
+ @result
35
+ end
36
+
37
+ alias_method :valid?, :pass?
38
+
39
+ # Returns true if the validations failed.
40
+ #
41
+ # @return [Boolean]
42
+ #
43
+ def fail?
44
+ not pass?
45
+ end
46
+
47
+ end # Result
48
+ end # Support
49
+ end # Ward
@@ -0,0 +1,147 @@
1
+ module Ward
2
+ # Pairs a context (or chain) with a matcher.
3
+ #
4
+ class Validator
5
+
6
+ # Returns the context.
7
+ #
8
+ # @return [Ward::ContextChain, Ward::Context]
9
+ #
10
+ attr_reader :context
11
+
12
+ # Returns the matcher.
13
+ #
14
+ # @return [Ward::Matchers::Matcher]
15
+ #
16
+ attr_reader :matcher
17
+
18
+ # The scenarios under which the validator will be run.
19
+ #
20
+ # @return [Enumerable<Symbol>]
21
+ #
22
+ attr_reader :scenarios
23
+
24
+ # An override to the default message.
25
+ #
26
+ # If set, this message will always be used if the validation fails,
27
+ # regardless of whether it is used as a positive or negative assertion.
28
+ #
29
+ # @return [Hash, String, nil]
30
+ # Returns a Hash if the user wishes to use a different message depending
31
+ # on the error state of the matcher (certain matchers, like Has, return
32
+ # a different error depending on what went wrong), a String if an
33
+ # explicit error message is set, or nil if no error message is set (in
34
+ # which case the default error for the matcher will be used).
35
+ #
36
+ attr_reader :message
37
+
38
+ # Creates a new Validator.
39
+ #
40
+ # @param [Ward::ContextChain, Ward::Context] context
41
+ # The context to be used to fetch the value for the matcher.
42
+ # @param [Ward::Matchers::Matcher] matcher
43
+ # A matcher to be used to validate the context value.
44
+ # @param [Hash] options
45
+ # Extra options used to customise the Validator.
46
+ #
47
+ # @option options [Symbol, Array<Symbol>] :scenarios
48
+ # An array of scenarios in which this validator should be run
49
+ # @option options [Hash{Symbol => String}, String] :message
50
+ # The error message to be used if the validator fails (see
51
+ # DSL::ValidationBuilder#message).
52
+ # @option options [String] :context_name
53
+ # The name to be used for the context when generating error messages.
54
+ #
55
+ def initialize(context, matcher, options = {})
56
+ @context, @matcher = context, matcher
57
+ @scenarios = Array(options[:scenarios] || :default).freeze
58
+ @negative = options[:negative] || false
59
+ @message = options[:message].freeze
60
+ @context_name = options[:context_name].freeze
61
+ end
62
+
63
+ # Determines if the validator is valid for the given record.
64
+ #
65
+ # The return value deviates from the ValidatorSet API -- where #valid?
66
+ # returns only a boolean value -- but that's fine since you shouldn't be
67
+ # calling Validator#valid? in your applications anyway. :)
68
+ #
69
+ # @param [Object] record
70
+ # The object whose attribute is to be validated.
71
+ #
72
+ # @return [Array<Boolean, {nil, String, Symbol}>]
73
+ # Returns an array with two elements. The first is always either true or
74
+ # false, indicating whether the validator passed, and the second is the
75
+ # raw error message returned by the matcher.
76
+ #
77
+ def validate(record)
78
+ # If the matches? method on the matcher takes two arguments, send in the
79
+ # record as well as the value.
80
+ result = if @matcher.method(:matches?).arity != 1
81
+ @matcher.matches?(@context.value(record), record)
82
+ else
83
+ @matcher.matches?(@context.value(record))
84
+ end
85
+
86
+ result, error = if defined?(Rubinius)
87
+ # Rubinius treats any value which responds to #to_a as being
88
+ # array-like, thus multiple assignment breaks if the matcher returns
89
+ # such an object.
90
+ result.is_a?(Array) ? result : [result].flatten
91
+ else
92
+ result
93
+ end
94
+
95
+ (!! result) ^ negative? ? [ true, nil ] : [ false, error_for(error) ]
96
+ end
97
+
98
+ # Returns if the validator should be run as part of the given scenario.
99
+ #
100
+ # @param [Symbol] scenario
101
+ # A name identifying the scenario.
102
+ #
103
+ # @return [Boolean]
104
+ #
105
+ def scenario?(scenario)
106
+ @scenarios.include?(scenario)
107
+ end
108
+
109
+ # Returns if the validator expects that the matcher _not_ match.
110
+ #
111
+ # Use with the +is_not+ and +does_not+ DSL keyword.
112
+ #
113
+ # @return [Boolean]
114
+ #
115
+ def negative?
116
+ @negative
117
+ end
118
+
119
+ private
120
+
121
+ # Returns the error message when the matcher fails.
122
+ #
123
+ # If the validator has a message defined, it will always be used in
124
+ # preference over any error returned by the matcher, or defiend in the
125
+ # language files.
126
+ #
127
+ # @param [nil, Symbol, String] key
128
+ # @return [String]
129
+ #
130
+ # @see Ward::Errors.error_for
131
+ # @see Ward::Matchers::Matcher#format_error
132
+ #
133
+ def error_for(key)
134
+ initial = @message || Ward::Errors.error_for(matcher, negative?, key)
135
+
136
+ error = initial % matcher.customise_error_values(
137
+ :expected => matcher.expected,
138
+ :context => @context_name || context.natural_name)
139
+
140
+ error.strip!
141
+ error[0] = error[0].chr.upcase
142
+
143
+ error
144
+ end
145
+
146
+ end
147
+ end # Ward
@@ -0,0 +1,115 @@
1
+ module Ward
2
+ # Holds one or more Validators which are associated with a particular class.
3
+ #
4
+ # This class is considered part of Ward's semi-public API; which is to say
5
+ # that it's methods should not be called in end-user applications, but are
6
+ # available for library and plugin authors who wish to extend it's
7
+ # functionality.
8
+ #
9
+ # @example Retrieving the validators for a class.
10
+ # MyClass.validators # => #<ValidatorSet [#<Validator>, ...]>
11
+ #
12
+ class ValidatorSet
13
+
14
+ # Builds a ValidatorSet using the given block.
15
+ #
16
+ # NOTE: Providing an existing ValidatorSet will result in a copy of that
17
+ # set being mutated; the original will not be changed.
18
+ #
19
+ # @param [Ward::ValidatorSet] set
20
+ # A ValidatorSet to which the built validators should be added.
21
+ #
22
+ # @return [Ward::ValidatorSet]
23
+ #
24
+ def self.build(set = nil, &block)
25
+ Ward::DSL::ValidationBlock.new(set, &block).to_validator_set
26
+ end
27
+
28
+ include Enumerable
29
+
30
+ # Creates a new ValidatorSet.
31
+ #
32
+ # @param [Enumerable<Ward::Validator>] validators
33
+ # An optional collection of validators.
34
+ #
35
+ def initialize(validators = [])
36
+ @validators = validators
37
+ end
38
+
39
+ # Determines if all of the contained validators are valid for the given
40
+ # record in the given scenario.
41
+ #
42
+ # @param [Object] record
43
+ # The object whose validations are to be run.
44
+ # @param [Symbol] scenario
45
+ # A name identifying the scenario.
46
+ #
47
+ # @return [Boolean]
48
+ # Returns true if the record validated, otherwise returns false.
49
+ #
50
+ def valid?(record, scenario = :default)
51
+ inject(true) do |result, validator|
52
+ (! validator.scenario?(scenario) or
53
+ validator.validate(record).first) and result
54
+ end
55
+ end
56
+
57
+ # A more useful version of #valid?
58
+ #
59
+ # Whereas #valid? simply returns true or false indicating whether the
60
+ # validations passed, #validate returns a Ward::Support::Result instance
61
+ # which describes the outcome of running the validators, and encapsulates
62
+ # any errors messages which resulted.
63
+ #
64
+ # @param [Object] record
65
+ # The object whose validations are to be run.
66
+ # @param [Symbol] scenario
67
+ # A name identifying the scenario.
68
+ #
69
+ # @return [Ward::Support::Result]
70
+ #
71
+ def validate(record, scenario = :default)
72
+ errors = Ward::Errors.new
73
+
74
+ @validators.each do |validator|
75
+ next unless validator.scenario?(scenario)
76
+ result, error = validator.validate(record)
77
+ errors.add(validator.context, error) unless result == true
78
+ end
79
+
80
+ Support::Result.new(errors)
81
+ end
82
+
83
+ # Iterates through each validator in the set.
84
+ #
85
+ # @yield [validator] Yields each validator in turn.
86
+ # @yieldparam [Ward::Validator]
87
+ #
88
+ def each(&block)
89
+ @validators.each(&block)
90
+ end
91
+
92
+ # Adds a new validator to set.
93
+ #
94
+ # @param [Ward::Validator] validator
95
+ # The validator to be added to the set.
96
+ #
97
+ def push(validator)
98
+ @validators << validator
99
+ end
100
+
101
+ alias_method :<<, :push
102
+
103
+ # Adds the validators contained in +other+ to the receiving set.
104
+ #
105
+ # @param [Ward::ValidatorSet] set
106
+ # The validator set whose validators are to be added to the receiver.
107
+ #
108
+ #
109
+ def merge!(other)
110
+ @validators |= other.to_a
111
+ self
112
+ end
113
+
114
+ end # ValidatorSet
115
+ end # Ward
@@ -0,0 +1,3 @@
1
+ module Ward
2
+ VERSION = File.read(File.expand_path('../../../VERSION', __FILE__)).strip
3
+ end
@@ -0,0 +1,15 @@
1
+ share_examples_for 'Has matcher relativity method' do
2
+ before(:all) do
3
+ @expectation ||= [5]
4
+ @matcher = Ward::Matchers::Has.new
5
+ @return = @matcher.__send__(@method, *@expectation)
6
+ end
7
+
8
+ it 'should return the matcher' do
9
+ @return.should == @matcher
10
+ end
11
+
12
+ it "should set the matcher relativity" do
13
+ @matcher.relativity.should == @relativity
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ # Ensures that a given class has a particular public instance method defined
2
+ # without instantiation. Works around Ruby 1.8 and 1.9 differences.
3
+ #
4
+ ::Spec::Matchers.define :have_public_method_defined do |value|
5
+ match do |klass|
6
+ klass.public_method_defined?(value.to_sym)
7
+ end
8
+
9
+ description do
10
+ "should have public instance method defined ##{value.to_s}"
11
+ end
12
+
13
+ failure_message_for_should do |klass|
14
+ "expected #{klass.inspect} to define public instance method " \
15
+ "#{value.inspect}, but it didn't"
16
+ end
17
+
18
+ failure_message_for_should_not do |klass|
19
+ "expected #{klass.inspect} to not define public instance method " \
20
+ "#{value.inspect}, but it did"
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ --exclude "spec"
2
+ --exclude "activesupport"
3
+ --exclude "rcov"
4
+ --sort coverage
5
+ --callsites
6
+ --xrefs
7
+ --profile
8
+ --text-summary
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --loadby random
3
+ --format progress
4
+ --backtrace
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'date' # Used in the CloseTo matcher spec.
5
+
6
+ require 'rubygems'
7
+ require 'spec'
8
+ require 'spec/autorun'
9
+
10
+ require 'ward'
11
+ require 'ward/spec'
12
+
13
+ # Spec libraries.
14
+ spec_libs = Dir.glob(File.expand_path(File.dirname(__FILE__)) + '/lib/**/*.rb')
15
+ spec_libs.each { |file| require file }
16
+
17
+ Spec::Runner.configure do |config|
18
+
19
+ end