aequitas 0.0.1

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 (216) hide show
  1. data/.gitignore +4 -0
  2. data/.rvmrc +1 -0
  3. data/Gemfile +6 -0
  4. data/LICENSE +21 -0
  5. data/README.rdoc +125 -0
  6. data/Rakefile +25 -0
  7. data/VERSION +1 -0
  8. data/aequitas.gemspec +20 -0
  9. data/lib/aequitas.rb +80 -0
  10. data/lib/aequitas/class_methods.rb +26 -0
  11. data/lib/aequitas/context.rb +53 -0
  12. data/lib/aequitas/contextual_rule_set.rb +221 -0
  13. data/lib/aequitas/exceptions.rb +10 -0
  14. data/lib/aequitas/macros.rb +466 -0
  15. data/lib/aequitas/message_transformer.rb +127 -0
  16. data/lib/aequitas/rule.rb +153 -0
  17. data/lib/aequitas/rule/absence.rb +14 -0
  18. data/lib/aequitas/rule/absence/blank.rb +21 -0
  19. data/lib/aequitas/rule/absence/nil.rb +21 -0
  20. data/lib/aequitas/rule/acceptance.rb +36 -0
  21. data/lib/aequitas/rule/block.rb +33 -0
  22. data/lib/aequitas/rule/confirmation.rb +41 -0
  23. data/lib/aequitas/rule/format.rb +81 -0
  24. data/lib/aequitas/rule/format/proc.rb +28 -0
  25. data/lib/aequitas/rule/format/regexp.rb +44 -0
  26. data/lib/aequitas/rule/formats/email.rb +52 -0
  27. data/lib/aequitas/rule/formats/url.rb +14 -0
  28. data/lib/aequitas/rule/guard.rb +51 -0
  29. data/lib/aequitas/rule/length.rb +87 -0
  30. data/lib/aequitas/rule/length/equal.rb +46 -0
  31. data/lib/aequitas/rule/length/maximum.rb +46 -0
  32. data/lib/aequitas/rule/length/minimum.rb +46 -0
  33. data/lib/aequitas/rule/length/range.rb +46 -0
  34. data/lib/aequitas/rule/method.rb +31 -0
  35. data/lib/aequitas/rule/numericalness.rb +85 -0
  36. data/lib/aequitas/rule/numericalness/equal.rb +30 -0
  37. data/lib/aequitas/rule/numericalness/greater_than.rb +30 -0
  38. data/lib/aequitas/rule/numericalness/greater_than_or_equal.rb +30 -0
  39. data/lib/aequitas/rule/numericalness/integer.rb +37 -0
  40. data/lib/aequitas/rule/numericalness/less_than.rb +30 -0
  41. data/lib/aequitas/rule/numericalness/less_than_or_equal.rb +30 -0
  42. data/lib/aequitas/rule/numericalness/non_integer.rb +64 -0
  43. data/lib/aequitas/rule/numericalness/not_equal.rb +30 -0
  44. data/lib/aequitas/rule/presence.rb +14 -0
  45. data/lib/aequitas/rule/presence/not_blank.rb +21 -0
  46. data/lib/aequitas/rule/presence/not_nil.rb +21 -0
  47. data/lib/aequitas/rule/primitive_type.rb +28 -0
  48. data/lib/aequitas/rule/skip_condition.rb +104 -0
  49. data/lib/aequitas/rule/within.rb +28 -0
  50. data/lib/aequitas/rule/within/range.rb +53 -0
  51. data/lib/aequitas/rule/within/range/bounded.rb +25 -0
  52. data/lib/aequitas/rule/within/range/unbounded_begin.rb +25 -0
  53. data/lib/aequitas/rule/within/range/unbounded_end.rb +25 -0
  54. data/lib/aequitas/rule/within/set.rb +39 -0
  55. data/lib/aequitas/rule_set.rb +80 -0
  56. data/lib/aequitas/support/blank.rb +22 -0
  57. data/lib/aequitas/support/equalizable.rb +113 -0
  58. data/lib/aequitas/support/ordered_hash.rb +438 -0
  59. data/lib/aequitas/version.rb +5 -0
  60. data/lib/aequitas/violation.rb +116 -0
  61. data/lib/aequitas/violation_set.rb +123 -0
  62. data/lib/aequitas/virtus.rb +29 -0
  63. data/lib/aequitas/virtus/inline_attribute_rule_extractor.rb +41 -0
  64. data/lib/aequitas/virtus/inline_attribute_rule_extractor/boolean.rb +17 -0
  65. data/lib/aequitas/virtus/inline_attribute_rule_extractor/object.rb +25 -0
  66. data/lib/aequitas/virtus/inline_attribute_rule_extractor/string.rb +27 -0
  67. data/spec/integration/aequitas/macros/validates_absence_of_spec.rb +19 -0
  68. data/spec/integration/aequitas/macros/validates_acceptance_of_spec.rb +19 -0
  69. data/spec/integration/aequitas/macros/validates_confirmation_of_spec.rb +25 -0
  70. data/spec/integration/aequitas/macros/validates_format_of_spec.rb +87 -0
  71. data/spec/integration/aequitas/macros/validates_length_of.rb +77 -0
  72. data/spec/integration/aequitas/macros/validates_numericalness_of_spec.rb +221 -0
  73. data/spec/integration/aequitas/macros/validates_presence_of_spec.rb +19 -0
  74. data/spec/integration/aequitas/macros/validates_with_block.rb +23 -0
  75. data/spec/integration/aequitas/macros/validates_with_method.rb +19 -0
  76. data/spec/integration/aequitas/macros/validates_within.rb +87 -0
  77. data/spec/integration/shared/macros/integration_spec.rb +67 -0
  78. data/spec/integration/virtus/boolean/presence_spec.rb +49 -0
  79. data/spec/integration/virtus/string/format/email_address_spec.rb +55 -0
  80. data/spec/integration/virtus/string/format/regexp_spec.rb +55 -0
  81. data/spec/integration/virtus/string/format/url_spec.rb +55 -0
  82. data/spec/integration/virtus/string/length/equal_spec.rb +49 -0
  83. data/spec/integration/virtus/string/length/range_spec.rb +49 -0
  84. data/spec/integration/virtus/string/presence_spec.rb +49 -0
  85. data/spec/rcov.opts +6 -0
  86. data/spec/spec_helper.rb +5 -0
  87. data/spec/suite.rb +5 -0
  88. data/spec/unit/aequitas/rule/absence/blank_spec.rb +45 -0
  89. data/spec/unit/aequitas/rule/acceptance_spec.rb +71 -0
  90. data/spec/unit/aequitas/rule/confirmation_spec.rb +80 -0
  91. data/spec/unit/aequitas/rule/guard_spec.rb +120 -0
  92. data/spec/unit/aequitas/rule/skip_condition_spec.rb +170 -0
  93. data/spec/unit/aequitas/rule_spec.rb +40 -0
  94. data/spec/unit/aequitas/support/blank_spec.rb +83 -0
  95. data/spec/unit/aequitas/support/equalizable/equalizer_spec.rb +21 -0
  96. data/spec/unit/aequitas/support/equalizable_spec.rb +67 -0
  97. data/spec/unit/aequitas/violation_set_spec.rb +154 -0
  98. data/spec_legacy/fixtures/barcode.rb +40 -0
  99. data/spec_legacy/fixtures/basketball_court.rb +58 -0
  100. data/spec_legacy/fixtures/basketball_player.rb +34 -0
  101. data/spec_legacy/fixtures/beta_tester_account.rb +33 -0
  102. data/spec_legacy/fixtures/bill_of_landing.rb +47 -0
  103. data/spec_legacy/fixtures/boat_dock.rb +26 -0
  104. data/spec_legacy/fixtures/city.rb +24 -0
  105. data/spec_legacy/fixtures/company.rb +93 -0
  106. data/spec_legacy/fixtures/corporate_world.rb +39 -0
  107. data/spec_legacy/fixtures/country.rb +24 -0
  108. data/spec_legacy/fixtures/ethernet_frame.rb +56 -0
  109. data/spec_legacy/fixtures/event.rb +44 -0
  110. data/spec_legacy/fixtures/g3_concert.rb +57 -0
  111. data/spec_legacy/fixtures/jabberwock.rb +27 -0
  112. data/spec_legacy/fixtures/kayak.rb +28 -0
  113. data/spec_legacy/fixtures/lernean_hydra.rb +39 -0
  114. data/spec_legacy/fixtures/llama_spaceship.rb +15 -0
  115. data/spec_legacy/fixtures/mathematical_function.rb +34 -0
  116. data/spec_legacy/fixtures/memory_object.rb +30 -0
  117. data/spec_legacy/fixtures/mittelschnauzer.rb +39 -0
  118. data/spec_legacy/fixtures/motor_launch.rb +21 -0
  119. data/spec_legacy/fixtures/multibyte.rb +16 -0
  120. data/spec_legacy/fixtures/page.rb +32 -0
  121. data/spec_legacy/fixtures/phone_number.rb +28 -0
  122. data/spec_legacy/fixtures/pirogue.rb +28 -0
  123. data/spec_legacy/fixtures/programming_language.rb +83 -0
  124. data/spec_legacy/fixtures/reservation.rb +38 -0
  125. data/spec_legacy/fixtures/scm_operation.rb +56 -0
  126. data/spec_legacy/fixtures/sms_message.rb +22 -0
  127. data/spec_legacy/fixtures/udp_packet.rb +49 -0
  128. data/spec_legacy/integration/absent_field_validator/absent_field_validator_spec.rb +90 -0
  129. data/spec_legacy/integration/absent_field_validator/spec_helper.rb +7 -0
  130. data/spec_legacy/integration/acceptance_validator/acceptance_validator_spec.rb +196 -0
  131. data/spec_legacy/integration/acceptance_validator/spec_helper.rb +7 -0
  132. data/spec_legacy/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +57 -0
  133. data/spec_legacy/integration/automatic_validation/disabling_inferred_validation_spec.rb +49 -0
  134. data/spec_legacy/integration/automatic_validation/inferred_boolean_properties_validation_spec.rb +100 -0
  135. data/spec_legacy/integration/automatic_validation/inferred_float_property_validation_spec.rb +45 -0
  136. data/spec_legacy/integration/automatic_validation/inferred_format_validation_spec.rb +35 -0
  137. data/spec_legacy/integration/automatic_validation/inferred_integer_properties_validation_spec.rb +70 -0
  138. data/spec_legacy/integration/automatic_validation/inferred_length_validation_spec.rb +142 -0
  139. data/spec_legacy/integration/automatic_validation/inferred_presence_validation_spec.rb +45 -0
  140. data/spec_legacy/integration/automatic_validation/inferred_primitive_validation_spec.rb +22 -0
  141. data/spec_legacy/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +48 -0
  142. data/spec_legacy/integration/automatic_validation/inferred_within_validation_spec.rb +35 -0
  143. data/spec_legacy/integration/automatic_validation/spec_helper.rb +57 -0
  144. data/spec_legacy/integration/block_validator/block_validator_spec.rb +32 -0
  145. data/spec_legacy/integration/block_validator/spec_helper.rb +5 -0
  146. data/spec_legacy/integration/conditional_validation/if_condition_spec.rb +63 -0
  147. data/spec_legacy/integration/conditional_validation/spec_helper.rb +5 -0
  148. data/spec_legacy/integration/confirmation_validator/confirmation_validator_spec.rb +76 -0
  149. data/spec_legacy/integration/confirmation_validator/spec_helper.rb +5 -0
  150. data/spec_legacy/integration/datamapper_models/association_validation_spec.rb +29 -0
  151. data/spec_legacy/integration/datamapper_models/inheritance_spec.rb +82 -0
  152. data/spec_legacy/integration/dirty_attributes/dirty_attributes_spec.rb +13 -0
  153. data/spec_legacy/integration/duplicated_validations/duplicated_validations_spec.rb +24 -0
  154. data/spec_legacy/integration/duplicated_validations/spec_helper.rb +5 -0
  155. data/spec_legacy/integration/format_validator/email_format_validator_spec.rb +139 -0
  156. data/spec_legacy/integration/format_validator/format_validator_spec.rb +64 -0
  157. data/spec_legacy/integration/format_validator/regexp_validator_spec.rb +33 -0
  158. data/spec_legacy/integration/format_validator/spec_helper.rb +5 -0
  159. data/spec_legacy/integration/format_validator/url_format_validator_spec.rb +93 -0
  160. data/spec_legacy/integration/length_validator/default_value_spec.rb +14 -0
  161. data/spec_legacy/integration/length_validator/equality_spec.rb +87 -0
  162. data/spec_legacy/integration/length_validator/error_message_spec.rb +22 -0
  163. data/spec_legacy/integration/length_validator/maximum_spec.rb +49 -0
  164. data/spec_legacy/integration/length_validator/minimum_spec.rb +54 -0
  165. data/spec_legacy/integration/length_validator/range_spec.rb +87 -0
  166. data/spec_legacy/integration/length_validator/spec_helper.rb +7 -0
  167. data/spec_legacy/integration/method_validator/method_validator_spec.rb +242 -0
  168. data/spec_legacy/integration/method_validator/spec_helper.rb +5 -0
  169. data/spec_legacy/integration/numeric_validator/equality_with_float_type_spec.rb +65 -0
  170. data/spec_legacy/integration/numeric_validator/equality_with_integer_type_spec.rb +41 -0
  171. data/spec_legacy/integration/numeric_validator/float_type_spec.rb +90 -0
  172. data/spec_legacy/integration/numeric_validator/gt_with_float_type_spec.rb +37 -0
  173. data/spec_legacy/integration/numeric_validator/gte_with_float_type_spec.rb +37 -0
  174. data/spec_legacy/integration/numeric_validator/integer_only_true_spec.rb +91 -0
  175. data/spec_legacy/integration/numeric_validator/integer_type_spec.rb +86 -0
  176. data/spec_legacy/integration/numeric_validator/lt_with_float_type_spec.rb +37 -0
  177. data/spec_legacy/integration/numeric_validator/lte_with_float_type_spec.rb +37 -0
  178. data/spec_legacy/integration/numeric_validator/spec_helper.rb +5 -0
  179. data/spec_legacy/integration/primitive_validator/primitive_validator_spec.rb +92 -0
  180. data/spec_legacy/integration/primitive_validator/spec_helper.rb +5 -0
  181. data/spec_legacy/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb +118 -0
  182. data/spec_legacy/integration/required_field_validator/association_spec.rb +69 -0
  183. data/spec_legacy/integration/required_field_validator/boolean_type_value_spec.rb +164 -0
  184. data/spec_legacy/integration/required_field_validator/date_type_value_spec.rb +127 -0
  185. data/spec_legacy/integration/required_field_validator/datetime_type_value_spec.rb +127 -0
  186. data/spec_legacy/integration/required_field_validator/float_type_value_spec.rb +131 -0
  187. data/spec_legacy/integration/required_field_validator/integer_type_value_spec.rb +99 -0
  188. data/spec_legacy/integration/required_field_validator/plain_old_ruby_object_spec.rb +35 -0
  189. data/spec_legacy/integration/required_field_validator/shared_examples.rb +26 -0
  190. data/spec_legacy/integration/required_field_validator/spec_helper.rb +7 -0
  191. data/spec_legacy/integration/required_field_validator/string_type_value_spec.rb +167 -0
  192. data/spec_legacy/integration/required_field_validator/text_type_value_spec.rb +49 -0
  193. data/spec_legacy/integration/shared/default_validation_context.rb +13 -0
  194. data/spec_legacy/integration/shared/valid_and_invalid_model.rb +35 -0
  195. data/spec_legacy/integration/uniqueness_validator/spec_helper.rb +5 -0
  196. data/spec_legacy/integration/uniqueness_validator/uniqueness_validator_spec.rb +116 -0
  197. data/spec_legacy/integration/within_validator/spec_helper.rb +5 -0
  198. data/spec_legacy/integration/within_validator/within_validator_spec.rb +168 -0
  199. data/spec_legacy/public/resource_spec.rb +105 -0
  200. data/spec_legacy/spec.opts +4 -0
  201. data/spec_legacy/spec_helper.rb +29 -0
  202. data/spec_legacy/unit/contextual_validators/emptiness_spec.rb +50 -0
  203. data/spec_legacy/unit/contextual_validators/execution_spec.rb +48 -0
  204. data/spec_legacy/unit/contextual_validators/spec_helper.rb +37 -0
  205. data/spec_legacy/unit/generic_validator/equality_operator_spec.rb +26 -0
  206. data/spec_legacy/unit/generic_validator/optional_spec.rb +54 -0
  207. data/spec_legacy/unit/validators/within_validator_spec.rb +23 -0
  208. data/spec_legacy/unit/violation_set/adding_spec.rb +54 -0
  209. data/spec_legacy/unit/violation_set/emptiness_spec.rb +38 -0
  210. data/spec_legacy/unit/violation_set/enumerable_spec.rb +32 -0
  211. data/spec_legacy/unit/violation_set/reading_spec.rb +35 -0
  212. data/spec_legacy/unit/violation_set/respond_to_spec.rb +15 -0
  213. data/tasks/spec.rake +38 -0
  214. data/tasks/yard.rake +9 -0
  215. data/tasks/yardstick.rake +19 -0
  216. metadata +302 -0
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use @aequitas --create
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in aequitas.gemspec
4
+ gemspec
5
+
6
+ gem 'dm-core', '~>1.3.0.beta', git: 'https://github.com/datamapper/dm-core'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2007 Guy van den Berg
2
+ Copyright (c) 2011 DataMapper development team
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,125 @@
1
+ This module provides validations for any Ruby class.
2
+
3
+ == Specifying Validations
4
+
5
+ There are two primary ways to implement validations
6
+
7
+ 1) Placing validation methods with properties as params in your class
8
+
9
+ require 'aequitas'
10
+
11
+ class ProgrammingLanguage
12
+ include Aequitas
13
+
14
+ attr_accessor :name
15
+
16
+ validates_presence_of :name
17
+ end
18
+
19
+ 2) (TODO) Using inferred validations on Virtus attributes, please see Aequitas::Inferred.
20
+ Note that not all validations that are provided via validation methods,
21
+ are also available as autovalidation options. If they are available,
22
+ they're functionally equivalent though.
23
+
24
+ class ProgrammingLanguage
25
+ include Virtus
26
+ include Aequitas
27
+
28
+ attribute :name, String, :required => true
29
+ end
30
+
31
+ See Aequitas::Macros to learn about the complete collection of validation rules available.
32
+
33
+ == Validating
34
+
35
+ Aequitas validations may be manually evaluated against a resource using the
36
+ `#valid?` method, which will return true if the resource is valid,
37
+ and false if it is invalid.
38
+
39
+ == Working with Validation Errors
40
+
41
+ If an instance fails one or more validation rules, Aequitas::Violation instances
42
+ will populate the Aequitas::ViolationSet object that is available through
43
+ the #errors method.
44
+
45
+ For example:
46
+
47
+ my_account = Account.new(:name => "Jose")
48
+ if my_account.valid?
49
+ # my_account is valid and has been saved
50
+ else
51
+ my_account.errors.each do |e|
52
+ puts e
53
+ end
54
+ end
55
+
56
+ See Aequitas::ViolationSet for all you can do with the #errors method.
57
+
58
+ == Contextual Validation
59
+
60
+ Aequitas also provide a means of grouping your validations into
61
+ contexts. This enables you to run different sets of validations when you
62
+ need it. For example, an instance may require separate validation rules
63
+ depending on its state: publishing, exporting, importing and so on.
64
+
65
+ Again, using our example for pure Ruby class validations:
66
+
67
+ class ProgrammingLanguage
68
+ include Virtus
69
+ include Aequitas
70
+
71
+ attribute :name, String
72
+
73
+ def ensure_allows_manual_memory_management
74
+ # ...
75
+ end
76
+
77
+ def ensure_allows_optional_parentheses
78
+ # ...
79
+ end
80
+
81
+ validates_presence_of :name
82
+ validates_with_method :ensure_allows_optional_parentheses, :when => [:implementing_a_dsl]
83
+ validates_with_method :ensure_allows_manual_memory_management, :when => [:doing_system_programming]
84
+ end
85
+
86
+ ProgrammingLanguage instance now use #valid? method with one of two context symbols:
87
+
88
+ @ruby.valid?(:implementing_a_dsl) # => true
89
+ @ruby.valid?(:doing_system_programming) # => false
90
+
91
+ @c.valid?(:implementing_a_dsl) # => false
92
+ @c.valid?(:doing_system_programming) # => true
93
+
94
+ Each context causes different set of validations to be triggered. If you don't
95
+ specify a context using :when, :on or :group options (they are all aliases and do
96
+ the same thing), default context name is :default. When you do model.valid? (without
97
+ specifying context explicitly), again, :default context is used. One validation
98
+ can be used in two, three or five contexts if you like:
99
+
100
+ class Book
101
+ include Virtus
102
+ include Aequitas
103
+
104
+ attribute :id, Serial
105
+ attribute :name, String
106
+
107
+ attribute :agreed_title, String
108
+ attribute :finished_toc, Boolean
109
+
110
+ # used in all contexts, including default
111
+ validates_presence_of :name, :when => [:default, :sending_to_print]
112
+ validates_presence_of :agreed_title, :when => [:sending_to_print]
113
+
114
+ validates_with_block :toc, :when => [:sending_to_print] do
115
+ if self.finished_toc
116
+ [true]
117
+ else
118
+ [false, "TOC must be finalized before you send a book to print"]
119
+ end
120
+ end
121
+ end
122
+
123
+ In the example above, name is validated for presence in both :default context and
124
+ :sending_to_print context, while TOC related block validation and title presence validation
125
+ only take place in :sending_to_print context.
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ gem 'jeweler', '~> 1.6.4'
6
+ require 'jeweler'
7
+
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = 'aequitas'
10
+ gem.summary = 'Library for performing validations on Ruby objects'
11
+ gem.description = gem.summary
12
+ gem.email = 'emmanuel.gomez@gmail.com'
13
+ gem.homepage = 'http://github.com/emmanuel/%s' % gem.name
14
+ gem.authors = [ 'Emmanuel Gomez' ]
15
+ gem.has_rdoc = 'yard'
16
+
17
+ gem.rubyforge_project = 'datamapper'
18
+ end
19
+
20
+ Jeweler::GemcutterTasks.new
21
+
22
+ FileList['tasks/**/*.rake'].each { |task| import task }
23
+ rescue LoadError
24
+ puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler -v 1.6.4'
25
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/aequitas.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "aequitas/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "aequitas"
7
+ s.version = Aequitas::VERSION
8
+ s.authors = ["Emmanuel Gomez"]
9
+ s.email = ["emmanuel.gomez@gmail.com"]
10
+ s.homepage = "https://github.com/emmanuel/aequitas"
11
+ s.summary = %q{Library for performing validations on Ruby objects.}
12
+ s.description = %q{Library for validating Ruby objects with rich metadata support.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_development_dependency("minitest", ["~> 2.8"])
20
+ end
data/lib/aequitas.rb ADDED
@@ -0,0 +1,80 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'aequitas/version'
4
+
5
+ require 'aequitas/class_methods'
6
+ require 'aequitas/violation_set'
7
+
8
+ module Aequitas
9
+
10
+ def self.included(base)
11
+ super
12
+ base.extend ClassMethods
13
+ end
14
+
15
+ # Check if a resource is valid in a given context
16
+ #
17
+ # @api public
18
+ def valid?(context_name = default_validation_context)
19
+ validate(context_name).errors.empty?
20
+ end
21
+
22
+ # Command a resource to populate its ViolationSet with any violations of
23
+ # its validation Rules in +context_name+
24
+ #
25
+ # @api public
26
+ def validate(context_name = default_validation_context)
27
+ # TODO: errors.replace(validation_violations(context_name))
28
+ errors.clear
29
+ validation_violations(context_name).each { |v| errors.add(v) }
30
+
31
+ self
32
+ end
33
+
34
+ # Get a list of violations for the receiver *without* mutating it
35
+ #
36
+ # @api private
37
+ def validation_violations(context_name = default_validation_context)
38
+ validation_rules.validate(self, context_name)
39
+ end
40
+
41
+ # @return [ViolationSet]
42
+ # the collection of current validation errors for this resource
43
+ #
44
+ # @api public
45
+ def errors
46
+ @errors ||= ViolationSet.new(self)
47
+ end
48
+
49
+ # The default validation context for this Resource.
50
+ # This Resource's default context can be overridden by implementing
51
+ # #default_validation_context
52
+ #
53
+ # @return [Symbol]
54
+ # the current validation context from the context stack
55
+ # (if valid for this model), or :default
56
+ #
57
+ # @api public
58
+ def default_validation_context
59
+ validation_rules.current_context
60
+ end
61
+
62
+ # @api private
63
+ def validation_rules
64
+ self.class.validation_rules
65
+ end
66
+
67
+ # Retrieve the value of the given property name for the purpose of validation.
68
+ # Default implementation is to send the attribute name arg to the receiver
69
+ # and use the resulting value as the attribute value for validation
70
+ #
71
+ # @param [Symbol] attribute_name
72
+ # the name of the attribute for which to retrieve
73
+ # the attribute value for validation.
74
+ #
75
+ # @api public
76
+ def validation_attribute_value(attribute_name)
77
+ __send__(attribute_name) if respond_to?(attribute_name, true)
78
+ end
79
+
80
+ end # module Aequitas
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'aequitas/contextual_rule_set'
4
+ require 'aequitas/macros'
5
+
6
+ module Aequitas
7
+ module ClassMethods
8
+ include Macros
9
+
10
+ # Return the ContextualRuleSet for this model
11
+ #
12
+ # @api public
13
+ def validation_rules
14
+ @validation_rules ||= ContextualRuleSet.new
15
+ end
16
+
17
+ private
18
+
19
+ # @api private
20
+ def inherited(base)
21
+ super
22
+ base.validation_rules.concat(validation_rules)
23
+ end
24
+
25
+ end # module ClassMethods
26
+ end # module Aequitas
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Aequitas
4
+ module Context
5
+ # Module with validation context functionality.
6
+ #
7
+ # Contexts are implemented using a thread-local array-based stack.
8
+
9
+
10
+ # Execute a block of code within a specific validation context
11
+ #
12
+ # @param [Symbol] context
13
+ # the context to execute the block of code within
14
+ #
15
+ # @api semipublic
16
+ def self.in_context(context)
17
+ stack << context
18
+ return_value = yield
19
+ ensure
20
+ stack.pop
21
+ return_value
22
+ end
23
+
24
+ # Get the current validation context or nil (if no context is on the stack).
25
+ #
26
+ # @return [Symbol, NilClass]
27
+ # The current validation context (for the current thread),
28
+ # or nil if no current context is on the stack
29
+ def self.current
30
+ stack.last
31
+ end
32
+
33
+ # Are there any contexts on the stack?
34
+ #
35
+ # @return [Boolean]
36
+ # true/false whether there are any contexts on the context stack
37
+ #
38
+ # @api semipublic
39
+ def self.any?(&block)
40
+ stack.any?(&block)
41
+ end
42
+
43
+ # The (thread-local) validation context stack
44
+ # This allows object graphs to be saved within potentially nested contexts
45
+ # without having to pass the validation context throughout
46
+ #
47
+ # @api private
48
+ def self.stack
49
+ Thread.current[:dm_validations_context_stack] ||= []
50
+ end
51
+
52
+ end # module Context
53
+ end # module Aequitas
@@ -0,0 +1,221 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'forwardable'
4
+ require 'aequitas/support/equalizable'
5
+ require 'aequitas/exceptions'
6
+ require 'aequitas/context'
7
+ require 'aequitas/rule_set'
8
+
9
+ module Aequitas
10
+ class ContextualRuleSet
11
+ extend Equalizable
12
+ extend Forwardable
13
+ include Enumerable
14
+
15
+ equalize_on :rule_sets
16
+
17
+ # MessageTransformer to use for transforming Violations on Resources
18
+ # instantiated from the model to which this ContextualRuleSet is bound
19
+ #
20
+ # @api public
21
+ # attr_accessor :transformer
22
+
23
+ # @api private
24
+ attr_reader :rule_sets
25
+
26
+ def_delegators :rule_sets, :each, :empty?
27
+
28
+ # Clear all named context rule sets
29
+ #
30
+ # @api public
31
+ def_delegators :rule_sets, :clear
32
+
33
+ def initialize
34
+ @rule_sets = Hash.new
35
+ define_context(:default)
36
+ end
37
+
38
+ def define_context(context_name)
39
+ rule_sets.fetch(context_name) do |context_name|
40
+ rule_sets[context_name] = RuleSet.new
41
+ end
42
+
43
+ self
44
+ end
45
+
46
+ # Delegate #validate to RuleSet
47
+ #
48
+ # @api public
49
+ def validate(resource, context_name)
50
+ context(context_name).validate(resource)
51
+ end
52
+
53
+ # Return the RuleSet for a given context name
54
+ #
55
+ # @param [String] name
56
+ # Context name for which to return a RuleSet
57
+ # @return [RuleSet]
58
+ # RuleSet for the given context
59
+ #
60
+ # @api public
61
+ def context(context_name)
62
+ rule_sets.fetch(context_name)
63
+ end
64
+
65
+ # Retrieve Rules applicable to a given attribute name
66
+ #
67
+ # @param [Symbol] attribute_name
68
+ # name of the attribute for which to retrieve applicable Rules
69
+ #
70
+ # @return [Array]
71
+ # list of Rules applicable to +attribute_name+
72
+ def [](attribute_name)
73
+ context(:default).fetch(attribute_name, [])
74
+ end
75
+
76
+ # Create a new rule of the given class for each name in +attribute_names+
77
+ # and add the rules to the RuleSet(s) indicated
78
+ #
79
+ # @param [Aequitas::Rule] rule_class
80
+ # Rule class, example: Aequitas::Rule::Presence
81
+ #
82
+ # @param [Array<Symbol>] attribute_names
83
+ # Attribute names given to validation macro, example:
84
+ # [:first_name, :last_name] in validates_presence_of :first_name, :last_name
85
+ #
86
+ # @param [Hash] options
87
+ # Options supplied to validation macro, example:
88
+ # {:context=>:default, :maximum=>50, :allow_nil=>true, :message=>nil}
89
+ #
90
+ # @option [Symbol] :context, :group, :when, :on
91
+ # the context in which the new rule should be run
92
+ #
93
+ # @return [self]
94
+ def add(rule_class, attribute_names, options = {}, &block)
95
+ context_names = extract_context_names(options)
96
+
97
+ attribute_names.each do |attribute_name|
98
+ rules = rule_class.rules_for(attribute_name, options, &block)
99
+
100
+ context_names.each { |context| add_rules_to_context(context, rules) }
101
+ end
102
+
103
+ self
104
+ end
105
+
106
+ # Assimilate all rules contained in +other+ into the receiver
107
+ #
108
+ # @param [ContextualRuleSet] other
109
+ # the ContextualRuleSet whose rules are to be assimilated
110
+ #
111
+ # @return [self]
112
+ def concat(other)
113
+ other.rule_sets.each do |context_name, rule_set|
114
+ add_rules_to_context(context_name, rules)
115
+ end
116
+
117
+ self
118
+ end
119
+
120
+ # Define a context and append rules to it
121
+ #
122
+ # @param [Symbol] context_name
123
+ # name of the context to define and append rules to
124
+ # @param [RuleSet, Array] rules
125
+ # Rules to append to +context_name+
126
+ #
127
+ # @return [self]
128
+ def add_rules_to_context(context_name, rules)
129
+ define_context(context_name)
130
+ context(context_name).concat(rules)
131
+
132
+ self
133
+ end
134
+
135
+ # Returns the current validation context on the stack if valid for this contextual rule set,
136
+ # nil if no RuleSets are defined (and no context names are on the validation context stack),
137
+ # or :default if the current context is invalid for this rule set or
138
+ # if no contexts have been defined for this contextual rule set and
139
+ # no context name is on the stack.
140
+ #
141
+ # @return [Symbol]
142
+ # the current validation context from the stack (if valid for this model),
143
+ # nil if no context name is on the stack and no contexts are defined for
144
+ # this model, or :default if the context on the stack is invalid for
145
+ # this model or no context is on the stack and this model has at least
146
+ # one validation context
147
+ #
148
+ # @api private
149
+ #
150
+ # TODO: this logic behind this method is too complicated.
151
+ # simplify the semantics of #current_context, #validate
152
+ def current_context
153
+ context = Aequitas::Context.current
154
+ valid_context?(context) ? context : :default
155
+ end
156
+
157
+ # Test if the validation context name is valid for this contextual rule set.
158
+ # A validation context name is valid if not nil and either:
159
+ # 1) no rule sets are defined for this contextual rule set
160
+ # OR
161
+ # 2) there is a rule set defined for the given context name
162
+ #
163
+ # @param [Symbol] context
164
+ # the context to test
165
+ #
166
+ # @return [Boolean]
167
+ # true if the context is valid
168
+ #
169
+ # @api private
170
+ def valid_context?(context_name)
171
+ !context_name.nil? && context_defined?(context_name)
172
+ end
173
+
174
+ def context_defined?(context_name)
175
+ rule_sets.include?(context_name)
176
+ end
177
+
178
+ # Assert that the given validation context name
179
+ # is valid for this contextual rule set
180
+ #
181
+ # @param [Symbol] context
182
+ # the context to test
183
+ #
184
+ # @raise [InvalidContextError]
185
+ # raised if the context is not valid for this contextual rule set
186
+ #
187
+ # @api private
188
+ #
189
+ # TODO: is this method actually needed?
190
+ def assert_valid_context(context_name)
191
+ unless valid_context?(context_name)
192
+ actual = context_name.inspect
193
+ expected = rule_sets.keys.inspect
194
+ raise InvalidContextError, "#{actual} is an invalid context, known contexts are #{expected}"
195
+ end
196
+ end
197
+
198
+ private
199
+
200
+ # Allow :context to be aliased to :group, :when & :on
201
+ #
202
+ # @param [Hash] options
203
+ # the options from which +context_names+ is to be extracted
204
+ #
205
+ # @return [Array(Symbol)]
206
+ # the context name(s) from +options+
207
+ #
208
+ # @api private
209
+ def extract_context_names(options)
210
+ context_name = [
211
+ options.delete(:context),
212
+ options.delete(:group),
213
+ options.delete(:when),
214
+ options.delete(:on)
215
+ ].compact.first
216
+
217
+ Array(context_name || :default)
218
+ end
219
+
220
+ end # class ContextualRuleSet
221
+ end # module Aequitas