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.
- 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
|