aequitas 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rvmrc +1 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.rdoc +125 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/aequitas.gemspec +20 -0
- data/lib/aequitas.rb +80 -0
- data/lib/aequitas/class_methods.rb +26 -0
- data/lib/aequitas/context.rb +53 -0
- data/lib/aequitas/contextual_rule_set.rb +221 -0
- data/lib/aequitas/exceptions.rb +10 -0
- data/lib/aequitas/macros.rb +466 -0
- data/lib/aequitas/message_transformer.rb +127 -0
- data/lib/aequitas/rule.rb +153 -0
- data/lib/aequitas/rule/absence.rb +14 -0
- data/lib/aequitas/rule/absence/blank.rb +21 -0
- data/lib/aequitas/rule/absence/nil.rb +21 -0
- data/lib/aequitas/rule/acceptance.rb +36 -0
- data/lib/aequitas/rule/block.rb +33 -0
- data/lib/aequitas/rule/confirmation.rb +41 -0
- data/lib/aequitas/rule/format.rb +81 -0
- data/lib/aequitas/rule/format/proc.rb +28 -0
- data/lib/aequitas/rule/format/regexp.rb +44 -0
- data/lib/aequitas/rule/formats/email.rb +52 -0
- data/lib/aequitas/rule/formats/url.rb +14 -0
- data/lib/aequitas/rule/guard.rb +51 -0
- data/lib/aequitas/rule/length.rb +87 -0
- data/lib/aequitas/rule/length/equal.rb +46 -0
- data/lib/aequitas/rule/length/maximum.rb +46 -0
- data/lib/aequitas/rule/length/minimum.rb +46 -0
- data/lib/aequitas/rule/length/range.rb +46 -0
- data/lib/aequitas/rule/method.rb +31 -0
- data/lib/aequitas/rule/numericalness.rb +85 -0
- data/lib/aequitas/rule/numericalness/equal.rb +30 -0
- data/lib/aequitas/rule/numericalness/greater_than.rb +30 -0
- data/lib/aequitas/rule/numericalness/greater_than_or_equal.rb +30 -0
- data/lib/aequitas/rule/numericalness/integer.rb +37 -0
- data/lib/aequitas/rule/numericalness/less_than.rb +30 -0
- data/lib/aequitas/rule/numericalness/less_than_or_equal.rb +30 -0
- data/lib/aequitas/rule/numericalness/non_integer.rb +64 -0
- data/lib/aequitas/rule/numericalness/not_equal.rb +30 -0
- data/lib/aequitas/rule/presence.rb +14 -0
- data/lib/aequitas/rule/presence/not_blank.rb +21 -0
- data/lib/aequitas/rule/presence/not_nil.rb +21 -0
- data/lib/aequitas/rule/primitive_type.rb +28 -0
- data/lib/aequitas/rule/skip_condition.rb +104 -0
- data/lib/aequitas/rule/within.rb +28 -0
- data/lib/aequitas/rule/within/range.rb +53 -0
- data/lib/aequitas/rule/within/range/bounded.rb +25 -0
- data/lib/aequitas/rule/within/range/unbounded_begin.rb +25 -0
- data/lib/aequitas/rule/within/range/unbounded_end.rb +25 -0
- data/lib/aequitas/rule/within/set.rb +39 -0
- data/lib/aequitas/rule_set.rb +80 -0
- data/lib/aequitas/support/blank.rb +22 -0
- data/lib/aequitas/support/equalizable.rb +113 -0
- data/lib/aequitas/support/ordered_hash.rb +438 -0
- data/lib/aequitas/version.rb +5 -0
- data/lib/aequitas/violation.rb +116 -0
- data/lib/aequitas/violation_set.rb +123 -0
- data/lib/aequitas/virtus.rb +29 -0
- data/lib/aequitas/virtus/inline_attribute_rule_extractor.rb +41 -0
- data/lib/aequitas/virtus/inline_attribute_rule_extractor/boolean.rb +17 -0
- data/lib/aequitas/virtus/inline_attribute_rule_extractor/object.rb +25 -0
- data/lib/aequitas/virtus/inline_attribute_rule_extractor/string.rb +27 -0
- data/spec/integration/aequitas/macros/validates_absence_of_spec.rb +19 -0
- data/spec/integration/aequitas/macros/validates_acceptance_of_spec.rb +19 -0
- data/spec/integration/aequitas/macros/validates_confirmation_of_spec.rb +25 -0
- data/spec/integration/aequitas/macros/validates_format_of_spec.rb +87 -0
- data/spec/integration/aequitas/macros/validates_length_of.rb +77 -0
- data/spec/integration/aequitas/macros/validates_numericalness_of_spec.rb +221 -0
- data/spec/integration/aequitas/macros/validates_presence_of_spec.rb +19 -0
- data/spec/integration/aequitas/macros/validates_with_block.rb +23 -0
- data/spec/integration/aequitas/macros/validates_with_method.rb +19 -0
- data/spec/integration/aequitas/macros/validates_within.rb +87 -0
- data/spec/integration/shared/macros/integration_spec.rb +67 -0
- data/spec/integration/virtus/boolean/presence_spec.rb +49 -0
- data/spec/integration/virtus/string/format/email_address_spec.rb +55 -0
- data/spec/integration/virtus/string/format/regexp_spec.rb +55 -0
- data/spec/integration/virtus/string/format/url_spec.rb +55 -0
- data/spec/integration/virtus/string/length/equal_spec.rb +49 -0
- data/spec/integration/virtus/string/length/range_spec.rb +49 -0
- data/spec/integration/virtus/string/presence_spec.rb +49 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/suite.rb +5 -0
- data/spec/unit/aequitas/rule/absence/blank_spec.rb +45 -0
- data/spec/unit/aequitas/rule/acceptance_spec.rb +71 -0
- data/spec/unit/aequitas/rule/confirmation_spec.rb +80 -0
- data/spec/unit/aequitas/rule/guard_spec.rb +120 -0
- data/spec/unit/aequitas/rule/skip_condition_spec.rb +170 -0
- data/spec/unit/aequitas/rule_spec.rb +40 -0
- data/spec/unit/aequitas/support/blank_spec.rb +83 -0
- data/spec/unit/aequitas/support/equalizable/equalizer_spec.rb +21 -0
- data/spec/unit/aequitas/support/equalizable_spec.rb +67 -0
- data/spec/unit/aequitas/violation_set_spec.rb +154 -0
- data/spec_legacy/fixtures/barcode.rb +40 -0
- data/spec_legacy/fixtures/basketball_court.rb +58 -0
- data/spec_legacy/fixtures/basketball_player.rb +34 -0
- data/spec_legacy/fixtures/beta_tester_account.rb +33 -0
- data/spec_legacy/fixtures/bill_of_landing.rb +47 -0
- data/spec_legacy/fixtures/boat_dock.rb +26 -0
- data/spec_legacy/fixtures/city.rb +24 -0
- data/spec_legacy/fixtures/company.rb +93 -0
- data/spec_legacy/fixtures/corporate_world.rb +39 -0
- data/spec_legacy/fixtures/country.rb +24 -0
- data/spec_legacy/fixtures/ethernet_frame.rb +56 -0
- data/spec_legacy/fixtures/event.rb +44 -0
- data/spec_legacy/fixtures/g3_concert.rb +57 -0
- data/spec_legacy/fixtures/jabberwock.rb +27 -0
- data/spec_legacy/fixtures/kayak.rb +28 -0
- data/spec_legacy/fixtures/lernean_hydra.rb +39 -0
- data/spec_legacy/fixtures/llama_spaceship.rb +15 -0
- data/spec_legacy/fixtures/mathematical_function.rb +34 -0
- data/spec_legacy/fixtures/memory_object.rb +30 -0
- data/spec_legacy/fixtures/mittelschnauzer.rb +39 -0
- data/spec_legacy/fixtures/motor_launch.rb +21 -0
- data/spec_legacy/fixtures/multibyte.rb +16 -0
- data/spec_legacy/fixtures/page.rb +32 -0
- data/spec_legacy/fixtures/phone_number.rb +28 -0
- data/spec_legacy/fixtures/pirogue.rb +28 -0
- data/spec_legacy/fixtures/programming_language.rb +83 -0
- data/spec_legacy/fixtures/reservation.rb +38 -0
- data/spec_legacy/fixtures/scm_operation.rb +56 -0
- data/spec_legacy/fixtures/sms_message.rb +22 -0
- data/spec_legacy/fixtures/udp_packet.rb +49 -0
- data/spec_legacy/integration/absent_field_validator/absent_field_validator_spec.rb +90 -0
- data/spec_legacy/integration/absent_field_validator/spec_helper.rb +7 -0
- data/spec_legacy/integration/acceptance_validator/acceptance_validator_spec.rb +196 -0
- data/spec_legacy/integration/acceptance_validator/spec_helper.rb +7 -0
- data/spec_legacy/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +57 -0
- data/spec_legacy/integration/automatic_validation/disabling_inferred_validation_spec.rb +49 -0
- data/spec_legacy/integration/automatic_validation/inferred_boolean_properties_validation_spec.rb +100 -0
- data/spec_legacy/integration/automatic_validation/inferred_float_property_validation_spec.rb +45 -0
- data/spec_legacy/integration/automatic_validation/inferred_format_validation_spec.rb +35 -0
- data/spec_legacy/integration/automatic_validation/inferred_integer_properties_validation_spec.rb +70 -0
- data/spec_legacy/integration/automatic_validation/inferred_length_validation_spec.rb +142 -0
- data/spec_legacy/integration/automatic_validation/inferred_presence_validation_spec.rb +45 -0
- data/spec_legacy/integration/automatic_validation/inferred_primitive_validation_spec.rb +22 -0
- data/spec_legacy/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +48 -0
- data/spec_legacy/integration/automatic_validation/inferred_within_validation_spec.rb +35 -0
- data/spec_legacy/integration/automatic_validation/spec_helper.rb +57 -0
- data/spec_legacy/integration/block_validator/block_validator_spec.rb +32 -0
- data/spec_legacy/integration/block_validator/spec_helper.rb +5 -0
- data/spec_legacy/integration/conditional_validation/if_condition_spec.rb +63 -0
- data/spec_legacy/integration/conditional_validation/spec_helper.rb +5 -0
- data/spec_legacy/integration/confirmation_validator/confirmation_validator_spec.rb +76 -0
- data/spec_legacy/integration/confirmation_validator/spec_helper.rb +5 -0
- data/spec_legacy/integration/datamapper_models/association_validation_spec.rb +29 -0
- data/spec_legacy/integration/datamapper_models/inheritance_spec.rb +82 -0
- data/spec_legacy/integration/dirty_attributes/dirty_attributes_spec.rb +13 -0
- data/spec_legacy/integration/duplicated_validations/duplicated_validations_spec.rb +24 -0
- data/spec_legacy/integration/duplicated_validations/spec_helper.rb +5 -0
- data/spec_legacy/integration/format_validator/email_format_validator_spec.rb +139 -0
- data/spec_legacy/integration/format_validator/format_validator_spec.rb +64 -0
- data/spec_legacy/integration/format_validator/regexp_validator_spec.rb +33 -0
- data/spec_legacy/integration/format_validator/spec_helper.rb +5 -0
- data/spec_legacy/integration/format_validator/url_format_validator_spec.rb +93 -0
- data/spec_legacy/integration/length_validator/default_value_spec.rb +14 -0
- data/spec_legacy/integration/length_validator/equality_spec.rb +87 -0
- data/spec_legacy/integration/length_validator/error_message_spec.rb +22 -0
- data/spec_legacy/integration/length_validator/maximum_spec.rb +49 -0
- data/spec_legacy/integration/length_validator/minimum_spec.rb +54 -0
- data/spec_legacy/integration/length_validator/range_spec.rb +87 -0
- data/spec_legacy/integration/length_validator/spec_helper.rb +7 -0
- data/spec_legacy/integration/method_validator/method_validator_spec.rb +242 -0
- data/spec_legacy/integration/method_validator/spec_helper.rb +5 -0
- data/spec_legacy/integration/numeric_validator/equality_with_float_type_spec.rb +65 -0
- data/spec_legacy/integration/numeric_validator/equality_with_integer_type_spec.rb +41 -0
- data/spec_legacy/integration/numeric_validator/float_type_spec.rb +90 -0
- data/spec_legacy/integration/numeric_validator/gt_with_float_type_spec.rb +37 -0
- data/spec_legacy/integration/numeric_validator/gte_with_float_type_spec.rb +37 -0
- data/spec_legacy/integration/numeric_validator/integer_only_true_spec.rb +91 -0
- data/spec_legacy/integration/numeric_validator/integer_type_spec.rb +86 -0
- data/spec_legacy/integration/numeric_validator/lt_with_float_type_spec.rb +37 -0
- data/spec_legacy/integration/numeric_validator/lte_with_float_type_spec.rb +37 -0
- data/spec_legacy/integration/numeric_validator/spec_helper.rb +5 -0
- data/spec_legacy/integration/primitive_validator/primitive_validator_spec.rb +92 -0
- data/spec_legacy/integration/primitive_validator/spec_helper.rb +5 -0
- data/spec_legacy/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb +118 -0
- data/spec_legacy/integration/required_field_validator/association_spec.rb +69 -0
- data/spec_legacy/integration/required_field_validator/boolean_type_value_spec.rb +164 -0
- data/spec_legacy/integration/required_field_validator/date_type_value_spec.rb +127 -0
- data/spec_legacy/integration/required_field_validator/datetime_type_value_spec.rb +127 -0
- data/spec_legacy/integration/required_field_validator/float_type_value_spec.rb +131 -0
- data/spec_legacy/integration/required_field_validator/integer_type_value_spec.rb +99 -0
- data/spec_legacy/integration/required_field_validator/plain_old_ruby_object_spec.rb +35 -0
- data/spec_legacy/integration/required_field_validator/shared_examples.rb +26 -0
- data/spec_legacy/integration/required_field_validator/spec_helper.rb +7 -0
- data/spec_legacy/integration/required_field_validator/string_type_value_spec.rb +167 -0
- data/spec_legacy/integration/required_field_validator/text_type_value_spec.rb +49 -0
- data/spec_legacy/integration/shared/default_validation_context.rb +13 -0
- data/spec_legacy/integration/shared/valid_and_invalid_model.rb +35 -0
- data/spec_legacy/integration/uniqueness_validator/spec_helper.rb +5 -0
- data/spec_legacy/integration/uniqueness_validator/uniqueness_validator_spec.rb +116 -0
- data/spec_legacy/integration/within_validator/spec_helper.rb +5 -0
- data/spec_legacy/integration/within_validator/within_validator_spec.rb +168 -0
- data/spec_legacy/public/resource_spec.rb +105 -0
- data/spec_legacy/spec.opts +4 -0
- data/spec_legacy/spec_helper.rb +29 -0
- data/spec_legacy/unit/contextual_validators/emptiness_spec.rb +50 -0
- data/spec_legacy/unit/contextual_validators/execution_spec.rb +48 -0
- data/spec_legacy/unit/contextual_validators/spec_helper.rb +37 -0
- data/spec_legacy/unit/generic_validator/equality_operator_spec.rb +26 -0
- data/spec_legacy/unit/generic_validator/optional_spec.rb +54 -0
- data/spec_legacy/unit/validators/within_validator_spec.rb +23 -0
- data/spec_legacy/unit/violation_set/adding_spec.rb +54 -0
- data/spec_legacy/unit/violation_set/emptiness_spec.rb +38 -0
- data/spec_legacy/unit/violation_set/enumerable_spec.rb +32 -0
- data/spec_legacy/unit/violation_set/reading_spec.rb +35 -0
- data/spec_legacy/unit/violation_set/respond_to_spec.rb +15 -0
- data/tasks/spec.rake +38 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +302 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use @aequitas --create
|
data/Gemfile
ADDED
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
|