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,5 @@
1
+ README.markdown
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,28 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+ Icon*
4
+
5
+ ## TEXTMATE
6
+ *.tmproj
7
+ tmtags
8
+
9
+ ## EMACS
10
+ *~
11
+ \#*
12
+ .\#*
13
+
14
+ ## VIM
15
+ *.swp
16
+
17
+ ## RUBINIUS
18
+ *.rbc
19
+
20
+ ## PROJECT::GENERAL
21
+ .yardoc
22
+ coverage
23
+ doc
24
+ measurements
25
+ pkg
26
+ rdoc
27
+
28
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Anthony Williams
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,99 @@
1
+ # Ward
2
+
3
+ _Ward is not ready for production use just yet. 0.1 should be considered a preview release only._
4
+
5
+ ## What Is It?
6
+
7
+ Object validation inspired by RSpec. As it turns out, Ward looks very little like RSpec, but that's where the inspiration came from nonetheless.
8
+
9
+ The aim is to provide an expressive DSL which allows you to compose validations without the need to create special validation methods.
10
+
11
+ class Person
12
+ include Ward::Validation
13
+
14
+ validate do |person|
15
+ person.subdomain.length.is(2..50)
16
+ person.has.at_least(5).posts
17
+ person.owner.name.is("Michael Scarn")
18
+ end
19
+ end
20
+
21
+ Note: The syntax described above isn't currently supported, though you can achieve a very similar end result with 0.1:
22
+
23
+ class Person
24
+ cattr_accessor :validators
25
+
26
+ # ...
27
+ end
28
+
29
+ Person.validators = Ward::ValidationSet.build do |person|
30
+ person.subdomain.length.is(2..50)
31
+ person.has.at_least(5).posts
32
+ person.owner.name.is("Michael Scarn")
33
+ end
34
+
35
+ Person.validators.valid?(Person.new)
36
+ # => false
37
+
38
+ Person.validators.validate(Person.new)
39
+ # => Ward::Support::Result
40
+
41
+ ### Current Status & Roadmap
42
+
43
+ **Planned for 0.2:**
44
+
45
+ * Add the Ward::Validation module allowing for more conventional validation
46
+ (defining the validations _on_ the object to be validated, as is the case
47
+ with ActiveRecord and dm-validations).
48
+
49
+ * Documentation is severely lacking, and will be improved. In the
50
+ meantime, Ward makes extensive use of Cucumber features (/features) where
51
+ you will find many examples of how to use the library in your own
52
+ applications.
53
+
54
+ * Ward error messages are currently in English only, but full i18n support is
55
+ planned.
56
+
57
+ **Planned for 0.3:**
58
+
59
+ * Compatibility modules will be added to support at least ActiveRecord and
60
+ DataMapper; and perhaps Sequel and other ORMs after that, depending on
61
+ demand.
62
+
63
+ **Later:**
64
+
65
+ * The DSL is subject to change prior to 1.0.
66
+
67
+ ### Compatibility
68
+
69
+ Ward specs are run against:
70
+
71
+ * Ruby (MRI) 1.8.6 p399,
72
+ * Ruby (MRI) 1.8.7 p249,
73
+ * Ruby (YARV) 1.9.1 p378,
74
+ * JRuby 1.4.0,
75
+ * Rubinius RC3.
76
+
77
+ Ward depends on ActiveSupport 3.0 to provide support for inflections in error messages, and to add Ruby 1.9-style String interpolation. However, only the bare minimum is included from ActiveSupport in order to minimise the impact on your runtime environment (see `lib/ward.rb` for specifics).
78
+
79
+ ### Note on Patches/Pull Requests
80
+
81
+ * Fork the project, taking care not to get any in your eyes.
82
+
83
+ * Make your feature addition or bug fix.
84
+
85
+ * Add tests for it. This is especially important not only because it helps
86
+ ensure that I don't unintentionally break it in a future version, but also
87
+ since it appeases Phyllis --- the goddess of Cucumbers --- who has been
88
+ known to rain showers of fresh vegetables on those who don't write tests.
89
+
90
+ * Commit, but do not mess with the Rakefile, VERSION, or history. If you want
91
+ to have your own version, that is fine, but bump version in a commit by
92
+ itself so that I can ignore it when I pull.
93
+
94
+ * Send me a pull request. Bonus points for topic branches. But we all know
95
+ everything is made up and the points don't matter.
96
+
97
+ ### Copyright
98
+
99
+ Copyright (c) 2010 Anthony Williams, MIT Licensed.
@@ -0,0 +1,47 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+
4
+ require File.expand_path('../lib/ward/version', __FILE__)
5
+
6
+ CLOBBER.include ['pkg', '*.gem', 'doc', 'coverage', 'measurements']
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gem|
11
+ gem.name = 'ward'
12
+ gem.summary = 'Ward'
13
+ gem.homepage = 'http://github.com/antw/ward'
14
+ gem.description = 'Object validation inspired by RSpec.'
15
+
16
+ gem.author = 'Anthony Williams'
17
+ gem.email = 'hi@antw.me'
18
+
19
+ gem.platform = Gem::Platform::RUBY
20
+ gem.has_rdoc = false
21
+
22
+ # Dependencies.
23
+ gem.add_dependency 'activesupport', '>= 3.0.0.beta'
24
+
25
+ # Development dependencies.
26
+ gem.add_development_dependency 'rspec', '>= 1.3.0'
27
+ gem.add_development_dependency 'cucumber', '>= 0.3'
28
+ gem.add_development_dependency 'yard', '>= 0.5'
29
+
30
+ gem.post_install_message =
31
+ "************************************************************\n" \
32
+ "\n" \
33
+ "Thank you for installing ward-#{Ward::VERSION}\n" \
34
+ "\n" \
35
+ "Please note that 0.1 is a preview release and considered\n" \
36
+ "unsuitable for use in a production environment.\n" \
37
+ "\n" \
38
+ "************************************************************\n" \
39
+ end
40
+
41
+ Jeweler::GemcutterTasks.new
42
+ rescue LoadError
43
+ puts 'Jeweler (or a dependency) not available. Install it with: gem ' \
44
+ 'install jeweler'
45
+ end
46
+
47
+ FileList['tasks/**/*.rake'].each { |task| import task }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,78 @@
1
+ Feature: Acceptance matcher
2
+ In order to validate that a user has accepted an attribute
3
+ I want to be able to ensure collections have a certain number of members
4
+
5
+ Background:
6
+ Given a class with a 'terms' attribute
7
+
8
+ Scenario Outline: When the attribute value is accepted
9
+ When using a validation set like
10
+ """
11
+ object.terms.is.accepted
12
+
13
+ """
14
+ And the instance 'terms' attribute is '<value>'
15
+ Then the validation set should pass
16
+
17
+ Examples:
18
+ | value |
19
+ | true |
20
+ | "true" |
21
+ | "t" |
22
+ | "yes" |
23
+ | "y" |
24
+ | 1 |
25
+ | "1" |
26
+
27
+ Scenario Outline: When the attribute value is not accepted
28
+ When using a validation set like
29
+ """
30
+ object.terms.is.accepted
31
+
32
+ """
33
+ And the instance 'terms' attribute is '<value>'
34
+ Then the validation set should fail
35
+
36
+ Examples:
37
+ | value |
38
+ | false |
39
+ | nil |
40
+ | "" |
41
+ | "false" |
42
+ | "f" |
43
+ | "no" |
44
+ | "n" |
45
+ | 0 |
46
+ | "0" |
47
+
48
+ Scenario: When setting a single custom expectation value
49
+ When using a validation set like
50
+ """
51
+ object.terms.is.accepted("absolutely")
52
+
53
+ """
54
+ And the instance 'terms' attribute is 'absolutely'
55
+ Then the validation set should pass
56
+
57
+ Scenario: When setting a single custom expectation value and the attribute is not accepted
58
+ When using a validation set like
59
+ """
60
+ object.terms.is.accepted("absolutely")
61
+
62
+ """
63
+ And the instance 'terms' attribute is 'yes'
64
+ Then the validation set should fail
65
+
66
+ Scenario Outline: When setting several custom expectation values
67
+ When using a validation set like
68
+ """
69
+ object.terms.is.accepted(%w( absolutely certainly ))
70
+
71
+ """
72
+ And the instance 'terms' attribute is '<value>'
73
+ Then the validation set should pass
74
+
75
+ Examples:
76
+ | value |
77
+ | absolutely |
78
+ | certainly |
@@ -0,0 +1,13 @@
1
+ Feature: Validating objects with an "attribute" keyword
2
+ In order validate attributes whose name conflicts with DSL methods
3
+ I want to be able to use the attribute keyword
4
+
5
+ Scenario: With no arguments, a matcher, and a valid value
6
+ Given a class with a 'message' attribute
7
+ When using a validation set like
8
+ """
9
+ object.attribute(:message).is.equal_to('Wow! Signal')
10
+
11
+ """
12
+ And the instance 'message' attribute is 'Wow! Signal'
13
+ Then the validation set should pass
@@ -0,0 +1,130 @@
1
+ Feature: CloseTo matcher
2
+ In order to validate values which may vary slightly at runtime
3
+ I want to be able to use the close_to matcher to set an acceptable range of values
4
+
5
+ Background:
6
+ Given a class with an 'estimate' attribute
7
+
8
+ Scenario Outline: When the attribute value is within the specified integer delta
9
+ When using a validation set like
10
+ """
11
+ object.estimate.is.close_to(50, 10)
12
+
13
+ """
14
+ And the instance 'estimate' attribute is '<value>'
15
+ Then the validation set should pass
16
+
17
+ Examples:
18
+ | value |
19
+ | 40 |
20
+ | 50 |
21
+ | 55.0 |
22
+ | 60 |
23
+
24
+ Scenario Outline: When the attribute value is outside the specified integer delta
25
+ When using a validation set like
26
+ """
27
+ object.estimate.is.close_to(50, 10)
28
+
29
+ """
30
+ And the instance 'estimate' attribute is '<value>'
31
+ Then the validation set should fail
32
+ And the error on 'estimate' should be 'Estimate should be within 10 of 50'
33
+
34
+ Examples:
35
+ | value |
36
+ | 0 |
37
+ | 39 |
38
+ | 61 |
39
+
40
+ Scenario Outline: When the attribute value is within the specified float delta
41
+ When using a validation set like
42
+ """
43
+ object.estimate.is.close_to(2, 0.5)
44
+
45
+ """
46
+ And the instance 'estimate' attribute is '<value>'
47
+ Then the validation set should pass
48
+
49
+ Examples:
50
+ | value |
51
+ | 1.5 |
52
+ | 2 |
53
+ | 2.0 |
54
+ | 2.5 |
55
+
56
+ Scenario Outline: When the attribute value is outside the specified float delta
57
+ When using a validation set like
58
+ """
59
+ object.estimate.is.close_to(2, 0.5)
60
+
61
+ """
62
+ And the instance 'estimate' attribute is '<value>'
63
+ Then the validation set should fail
64
+ And the error on 'estimate' should be 'Estimate should be within 0.5 of 2'
65
+
66
+ Examples:
67
+ | value |
68
+ | 0 |
69
+ | 1.49 |
70
+ | 2.51 |
71
+
72
+ Scenario Outline: When the attribute value is a date inside the delta
73
+ When using a validation set like
74
+ """
75
+ object.estimate.is.close_to(Date.civil(2010, 3, 1), 1)
76
+
77
+ """
78
+ And the instance 'estimate' attribute is '<value>'
79
+ Then the validation set should pass
80
+
81
+ Examples:
82
+ | value |
83
+ | Date.civil(2010, 3, 1) - 1 |
84
+ | Date.civil(2010, 3, 1) |
85
+ | Date.civil(2010, 3, 1) + 1 |
86
+
87
+ Scenario Outline: When the attribute value is a date outside the delta
88
+ When using a validation set like
89
+ """
90
+ object.estimate.is.close_to(Date.civil(2010, 3, 1), 1)
91
+
92
+ """
93
+ And the instance 'estimate' attribute is '<value>'
94
+ Then the validation set should fail
95
+ And the error on 'estimate' should be 'Estimate should be within 1 of 2010-03-01'
96
+
97
+ Examples:
98
+ | value |
99
+ | Date.civil(2010, 3, 1) - 2 |
100
+ | Date.civil(2010, 3, 1) + 2 |
101
+
102
+ Scenario Outline: When the attribute value is a date inside the delta
103
+ When using a validation set like
104
+ """
105
+ object.estimate.is.close_to(Time.parse('2010-03-01 12:00'), 30)
106
+
107
+ """
108
+ And the instance 'estimate' attribute is '<value>'
109
+ Then the validation set should pass
110
+
111
+ Examples:
112
+ | value |
113
+ | Time.parse('2010-03-01 12:00') - 30 |
114
+ | Time.parse('2010-03-01 12:00') |
115
+ | Time.parse('2010-03-01 12:00') + 30 |
116
+
117
+ Scenario Outline: When the attribute value is a date outside the delta
118
+ When using a validation set like
119
+ """
120
+ object.estimate.is.close_to(Time.parse('2010-03-01 12:00'), 30)
121
+
122
+ """
123
+ And the instance 'estimate' attribute is '<value>'
124
+ Then the validation set should fail
125
+ And the error on 'estimate' should be '/^Estimate should be within 30 of/'
126
+
127
+ Examples:
128
+ | value |
129
+ | Time.parse('2010-03-01 12:00') - 31 |
130
+ | Time.parse('2010-03-01 12:00') + 31 |
@@ -0,0 +1,47 @@
1
+ Feature: Validating values which require an argument or block
2
+
3
+ Scenario: Supplying an argument to a context
4
+ Given the class has behaviour like
5
+ """
6
+ def name(yes = false)
7
+ if yes then "True" else "False" end
8
+ end
9
+
10
+ """
11
+ And using a validation set like
12
+ """
13
+ object.name(true).equal_to("True")
14
+
15
+ """
16
+ Then the validation set should pass
17
+
18
+ Scenario: Supplying an argument to a context when using scenarios
19
+ Given the class has behaviour like
20
+ """
21
+ def name(yes = false)
22
+ if yes then "True" else "False" end
23
+ end
24
+
25
+ """
26
+ And using a validation set like
27
+ """
28
+ object.name(true).equal_to("True")
29
+ object.name(false).equal_to("False").scenario(:negative)
30
+
31
+ """
32
+ Then the validation set should pass when using the 'negative' scenario
33
+
34
+ Scenario: Supplying a block to a context
35
+ Given the class has behaviour like
36
+ """
37
+ def name(&block)
38
+ block.call.reverse
39
+ end
40
+
41
+ """
42
+ And using a validation set like
43
+ """
44
+ object.name { "My value" }.equal_to("eulav yM")
45
+
46
+ """
47
+ Then the validation set should pass