sbf-dm-validations 1.3.0.beta
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.
- checksums.yaml +7 -0
- data/.gitignore +38 -0
- data/.rspec +1 -0
- data/.rubocop.yml +468 -0
- data/.travis.yml +51 -0
- data/Gemfile +60 -0
- data/LICENSE +21 -0
- data/README.rdoc +122 -0
- data/Rakefile +4 -0
- data/dm-validations.gemspec +20 -0
- data/lib/data_mapper/core.rb +1 -0
- data/lib/data_mapper/support/assertions.rb +1 -0
- data/lib/data_mapper/support/equalizer.rb +1 -0
- data/lib/data_mapper/support/ordered_set.rb +2 -0
- data/lib/data_mapper/validation/backward.rb +205 -0
- data/lib/data_mapper/validation/context.rb +57 -0
- data/lib/data_mapper/validation/contextual_rule_set.rb +210 -0
- data/lib/data_mapper/validation/exceptions.rb +7 -0
- data/lib/data_mapper/validation/inferred.rb +264 -0
- data/lib/data_mapper/validation/macros.rb +449 -0
- data/lib/data_mapper/validation/message_transformer.rb +111 -0
- data/lib/data_mapper/validation/model_extensions.rb +17 -0
- data/lib/data_mapper/validation/resource_extensions.rb +131 -0
- data/lib/data_mapper/validation/rule/absence.rb +31 -0
- data/lib/data_mapper/validation/rule/acceptance.rb +49 -0
- data/lib/data_mapper/validation/rule/block.rb +37 -0
- data/lib/data_mapper/validation/rule/confirmation.rb +47 -0
- data/lib/data_mapper/validation/rule/format/proc.rb +34 -0
- data/lib/data_mapper/validation/rule/format/regexp.rb +51 -0
- data/lib/data_mapper/validation/rule/format.rb +86 -0
- data/lib/data_mapper/validation/rule/formats/email.rb +54 -0
- data/lib/data_mapper/validation/rule/formats/url.rb +13 -0
- data/lib/data_mapper/validation/rule/length/equal.rb +48 -0
- data/lib/data_mapper/validation/rule/length/maximum.rb +50 -0
- data/lib/data_mapper/validation/rule/length/minimum.rb +50 -0
- data/lib/data_mapper/validation/rule/length/range.rb +50 -0
- data/lib/data_mapper/validation/rule/length.rb +96 -0
- data/lib/data_mapper/validation/rule/method.rb +42 -0
- data/lib/data_mapper/validation/rule/numericalness/equal.rb +34 -0
- data/lib/data_mapper/validation/rule/numericalness/greater_than.rb +34 -0
- data/lib/data_mapper/validation/rule/numericalness/greater_than_or_equal.rb +34 -0
- data/lib/data_mapper/validation/rule/numericalness/integer.rb +41 -0
- data/lib/data_mapper/validation/rule/numericalness/less_than.rb +34 -0
- data/lib/data_mapper/validation/rule/numericalness/less_than_or_equal.rb +34 -0
- data/lib/data_mapper/validation/rule/numericalness/not_equal.rb +34 -0
- data/lib/data_mapper/validation/rule/numericalness/numeric.rb +68 -0
- data/lib/data_mapper/validation/rule/numericalness.rb +91 -0
- data/lib/data_mapper/validation/rule/presence.rb +52 -0
- data/lib/data_mapper/validation/rule/primitive_type.rb +32 -0
- data/lib/data_mapper/validation/rule/uniqueness.rb +64 -0
- data/lib/data_mapper/validation/rule/within/range/bounded.rb +29 -0
- data/lib/data_mapper/validation/rule/within/range/unbounded_begin.rb +29 -0
- data/lib/data_mapper/validation/rule/within/range/unbounded_end.rb +29 -0
- data/lib/data_mapper/validation/rule/within/range.rb +55 -0
- data/lib/data_mapper/validation/rule/within/set.rb +45 -0
- data/lib/data_mapper/validation/rule/within.rb +32 -0
- data/lib/data_mapper/validation/rule.rb +232 -0
- data/lib/data_mapper/validation/rule_set.rb +157 -0
- data/lib/data_mapper/validation/support/object.rb +19 -0
- data/lib/data_mapper/validation/support/ordered_hash.rb +434 -0
- data/lib/data_mapper/validation/version.rb +5 -0
- data/lib/data_mapper/validation/violation.rb +136 -0
- data/lib/data_mapper/validation/violation_set.rb +115 -0
- data/lib/data_mapper/validation.rb +105 -0
- data/lib/dm-validations.rb +24 -0
- data/spec/data_mapper/validation/resource_extensions/save_spec.rb +56 -0
- data/spec/data_mapper/validation/resource_extensions/validate_spec.rb +103 -0
- data/spec/fixtures/barcode.rb +40 -0
- data/spec/fixtures/basketball_court.rb +58 -0
- data/spec/fixtures/basketball_player.rb +34 -0
- data/spec/fixtures/beta_tester_account.rb +33 -0
- data/spec/fixtures/bill_of_landing.rb +47 -0
- data/spec/fixtures/boat_dock.rb +26 -0
- data/spec/fixtures/city.rb +24 -0
- data/spec/fixtures/company.rb +93 -0
- data/spec/fixtures/corporate_world.rb +39 -0
- data/spec/fixtures/country.rb +24 -0
- data/spec/fixtures/ethernet_frame.rb +56 -0
- data/spec/fixtures/event.rb +44 -0
- data/spec/fixtures/g3_concert.rb +57 -0
- data/spec/fixtures/integer_dumped_as_string_property.rb +24 -0
- data/spec/fixtures/jabberwock.rb +27 -0
- data/spec/fixtures/kayak.rb +28 -0
- data/spec/fixtures/lernean_hydra.rb +39 -0
- data/spec/fixtures/llama_spaceship.rb +15 -0
- data/spec/fixtures/mathematical_function.rb +34 -0
- data/spec/fixtures/memory_object.rb +35 -0
- data/spec/fixtures/mittelschnauzer.rb +39 -0
- data/spec/fixtures/motor_launch.rb +21 -0
- data/spec/fixtures/multibyte.rb +16 -0
- data/spec/fixtures/page.rb +32 -0
- data/spec/fixtures/phone_number.rb +28 -0
- data/spec/fixtures/pirogue.rb +28 -0
- data/spec/fixtures/programming_language.rb +83 -0
- data/spec/fixtures/reservation.rb +38 -0
- data/spec/fixtures/scm_operation.rb +56 -0
- data/spec/fixtures/sms_message.rb +22 -0
- data/spec/fixtures/udp_packet.rb +49 -0
- data/spec/integration/absent_field_validator/absent_field_validator_spec.rb +90 -0
- data/spec/integration/absent_field_validator/spec_helper.rb +7 -0
- data/spec/integration/acceptance_validator/acceptance_validator_spec.rb +196 -0
- data/spec/integration/acceptance_validator/spec_helper.rb +7 -0
- data/spec/integration/automatic_validation/custom_messages_for_inferred_validation_spec.rb +57 -0
- data/spec/integration/automatic_validation/disabling_inferred_validation_spec.rb +49 -0
- data/spec/integration/automatic_validation/inferred_boolean_properties_validation_spec.rb +100 -0
- data/spec/integration/automatic_validation/inferred_float_property_validation_spec.rb +45 -0
- data/spec/integration/automatic_validation/inferred_format_validation_spec.rb +35 -0
- data/spec/integration/automatic_validation/inferred_integer_properties_validation_spec.rb +70 -0
- data/spec/integration/automatic_validation/inferred_length_validation_spec.rb +142 -0
- data/spec/integration/automatic_validation/inferred_presence_validation_spec.rb +45 -0
- data/spec/integration/automatic_validation/inferred_primitive_validation_spec.rb +22 -0
- data/spec/integration/automatic_validation/inferred_uniqueness_validation_spec.rb +48 -0
- data/spec/integration/automatic_validation/inferred_within_validation_spec.rb +35 -0
- data/spec/integration/automatic_validation/spec_helper.rb +57 -0
- data/spec/integration/block_validator/spec_helper.rb +5 -0
- data/spec/integration/conditional_validation/if_condition_spec.rb +63 -0
- data/spec/integration/conditional_validation/spec_helper.rb +5 -0
- data/spec/integration/confirmation_validator/confirmation_validator_spec.rb +76 -0
- data/spec/integration/confirmation_validator/spec_helper.rb +5 -0
- data/spec/integration/datamapper_models/association_validation_spec.rb +29 -0
- data/spec/integration/datamapper_models/inheritance_spec.rb +82 -0
- data/spec/integration/dirty_attributes/dirty_attributes_spec.rb +13 -0
- data/spec/integration/duplicated_validations/duplicated_validations_spec.rb +24 -0
- data/spec/integration/duplicated_validations/spec_helper.rb +5 -0
- data/spec/integration/format_validator/email_format_validator_spec.rb +139 -0
- data/spec/integration/format_validator/format_validator_spec.rb +64 -0
- data/spec/integration/format_validator/regexp_validator_spec.rb +33 -0
- data/spec/integration/format_validator/spec_helper.rb +5 -0
- data/spec/integration/format_validator/url_format_validator_spec.rb +91 -0
- data/spec/integration/length_validator/default_value_spec.rb +14 -0
- data/spec/integration/length_validator/equality_spec.rb +83 -0
- data/spec/integration/length_validator/error_message_spec.rb +22 -0
- data/spec/integration/length_validator/maximum_spec.rb +47 -0
- data/spec/integration/length_validator/minimum_spec.rb +54 -0
- data/spec/integration/length_validator/range_spec.rb +87 -0
- data/spec/integration/length_validator/spec_helper.rb +7 -0
- data/spec/integration/method_validator/method_validator_spec.rb +243 -0
- data/spec/integration/method_validator/spec_helper.rb +5 -0
- data/spec/integration/numeric_validator/equality_with_float_type_spec.rb +65 -0
- data/spec/integration/numeric_validator/equality_with_integer_type_spec.rb +41 -0
- data/spec/integration/numeric_validator/float_type_spec.rb +90 -0
- data/spec/integration/numeric_validator/gt_with_float_type_spec.rb +37 -0
- data/spec/integration/numeric_validator/gte_with_float_type_spec.rb +36 -0
- data/spec/integration/numeric_validator/integer_only_true_spec.rb +91 -0
- data/spec/integration/numeric_validator/integer_type_spec.rb +86 -0
- data/spec/integration/numeric_validator/lt_with_float_type_spec.rb +37 -0
- data/spec/integration/numeric_validator/lte_with_float_type_spec.rb +37 -0
- data/spec/integration/numeric_validator/spec_helper.rb +5 -0
- data/spec/integration/primitive_validator/primitive_validator_spec.rb +112 -0
- data/spec/integration/primitive_validator/spec_helper.rb +5 -0
- data/spec/integration/pure_ruby_objects/plain_old_ruby_object_validation_spec.rb +118 -0
- data/spec/integration/required_field_validator/association_spec.rb +69 -0
- data/spec/integration/required_field_validator/boolean_type_value_spec.rb +164 -0
- data/spec/integration/required_field_validator/date_type_value_spec.rb +127 -0
- data/spec/integration/required_field_validator/datetime_type_value_spec.rb +127 -0
- data/spec/integration/required_field_validator/float_type_value_spec.rb +131 -0
- data/spec/integration/required_field_validator/integer_type_value_spec.rb +99 -0
- data/spec/integration/required_field_validator/plain_old_ruby_object_spec.rb +35 -0
- data/spec/integration/required_field_validator/shared_examples.rb +27 -0
- data/spec/integration/required_field_validator/spec_helper.rb +7 -0
- data/spec/integration/required_field_validator/string_type_value_spec.rb +167 -0
- data/spec/integration/required_field_validator/text_type_value_spec.rb +49 -0
- data/spec/integration/shared/default_validation_context.rb +13 -0
- data/spec/integration/shared/valid_and_invalid_model.rb +35 -0
- data/spec/integration/uniqueness_validator/spec_helper.rb +5 -0
- data/spec/integration/uniqueness_validator/uniqueness_validator_spec.rb +116 -0
- data/spec/integration/within_validator/spec_helper.rb +5 -0
- data/spec/integration/within_validator/within_validator_spec.rb +168 -0
- data/spec/public/resource_spec.rb +113 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/unit/contextual_validators/emptiness_spec.rb +50 -0
- data/spec/unit/contextual_validators/execution_spec.rb +48 -0
- data/spec/unit/contextual_validators/spec_helper.rb +37 -0
- data/spec/unit/generic_validator/equality_operator_spec.rb +26 -0
- data/spec/unit/generic_validator/optional_spec.rb +54 -0
- data/spec/unit/validators/within_validator_spec.rb +23 -0
- data/spec/unit/violation_set/adding_spec.rb +54 -0
- data/spec/unit/violation_set/emptiness_spec.rb +38 -0
- data/spec/unit/violation_set/enumerable_spec.rb +32 -0
- data/spec/unit/violation_set/reading_spec.rb +35 -0
- data/spec/unit/violation_set/respond_to_spec.rb +15 -0
- data/tasks/spec.rake +21 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +245 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
# for options_with_message
|
|
3
|
+
# TODO: rename :auto_validation => :infer_validation
|
|
4
|
+
Property.accept_options :auto_validation, :validates, :set, :format, :message, :messages
|
|
5
|
+
|
|
6
|
+
module Validation
|
|
7
|
+
module Inferred
|
|
8
|
+
|
|
9
|
+
# @api private
|
|
10
|
+
def property(*)
|
|
11
|
+
property = super
|
|
12
|
+
|
|
13
|
+
if property.options.fetch(:auto_validation, true) && !disabled_auto_validations?
|
|
14
|
+
rule_definitions = Validation::Inferred.rules_for_property(property)
|
|
15
|
+
rule_definitions.each do |rule_class, attribute_name, options|
|
|
16
|
+
validation_rules.add(rule_class, [attribute_name], options)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# FIXME: explicit return needed for YARD to parse this properly
|
|
21
|
+
return property
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# attr_accessor :infer_validations
|
|
25
|
+
|
|
26
|
+
# TODO: replace the @disabled_auto_validations reader methods with
|
|
27
|
+
# a positive statement instead of negative (instead of skip/disable, etc)
|
|
28
|
+
# eg., @infer_validations, #infer_validations?
|
|
29
|
+
#
|
|
30
|
+
# Checks whether auto validations are currently
|
|
31
|
+
# disabled (see +disable_auto_validations+ method
|
|
32
|
+
# that takes a block)
|
|
33
|
+
#
|
|
34
|
+
# @return [TrueClass, FalseClass]
|
|
35
|
+
# true if auto validation is currently disabled
|
|
36
|
+
#
|
|
37
|
+
# @api public
|
|
38
|
+
# def infer_validations?
|
|
39
|
+
# defined?(@infer_validations) ? @infer_validations : true
|
|
40
|
+
# end
|
|
41
|
+
|
|
42
|
+
# TODO: why are there 3 entry points to this ivar?
|
|
43
|
+
# #disable_auto_validations, #disabled_auto_validations?, #auto_validations_disabled?
|
|
44
|
+
attr_reader :disable_auto_validations
|
|
45
|
+
|
|
46
|
+
# Checks whether auto validations are currently
|
|
47
|
+
# disabled (see +disable_auto_validations+ method
|
|
48
|
+
# that takes a block)
|
|
49
|
+
#
|
|
50
|
+
# @return [TrueClass, FalseClass]
|
|
51
|
+
# true if auto validation is currently disabled
|
|
52
|
+
#
|
|
53
|
+
# @api semipublic
|
|
54
|
+
def disabled_auto_validations?
|
|
55
|
+
@disable_auto_validations || false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# TODO: deprecate all but one of these 3 variants
|
|
59
|
+
alias_method :auto_validations_disabled?, :disabled_auto_validations?
|
|
60
|
+
|
|
61
|
+
# Disable generation of validations for the duration of the given block
|
|
62
|
+
#
|
|
63
|
+
# @api public
|
|
64
|
+
def without_auto_validations
|
|
65
|
+
previous, @disable_auto_validations = @disable_auto_validations, true
|
|
66
|
+
yield
|
|
67
|
+
ensure
|
|
68
|
+
@disable_auto_validations = previous
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Infer validations for a given property. This will only occur
|
|
73
|
+
# if the option :auto_validation is either true or left undefined.
|
|
74
|
+
#
|
|
75
|
+
# Triggers that generate validator creation
|
|
76
|
+
#
|
|
77
|
+
# :required => true
|
|
78
|
+
# Setting the option :required to true causes a Rule::Presence
|
|
79
|
+
# to be created for the property
|
|
80
|
+
#
|
|
81
|
+
# :length => 20
|
|
82
|
+
# Setting the option :length causes a Rule::Length to be created
|
|
83
|
+
# for the property.
|
|
84
|
+
# If the value is a Integer the Rule will have :maximum => value.
|
|
85
|
+
# If the value is a Range the Rule will have :within => value.
|
|
86
|
+
#
|
|
87
|
+
# :format => :predefined / lambda / Proc
|
|
88
|
+
# Setting the :format option causes a Rule::Format to be created
|
|
89
|
+
# for the property
|
|
90
|
+
#
|
|
91
|
+
# :set => ["foo", "bar", "baz"]
|
|
92
|
+
# Setting the :set option causes a Rule::Within to be created
|
|
93
|
+
# for the property
|
|
94
|
+
#
|
|
95
|
+
# Integer type
|
|
96
|
+
# Using a Integer type causes a Rule::Numericalness to be created
|
|
97
|
+
# for the property. The Rule's :integer_only option is set to true
|
|
98
|
+
#
|
|
99
|
+
# BigDecimal or Float type
|
|
100
|
+
# Using a Integer type causes a Rule::Numericalness to be created
|
|
101
|
+
# for the property. The Rule's :integer_only option will be set
|
|
102
|
+
# to false, and precision/scale will be set to match the Property
|
|
103
|
+
#
|
|
104
|
+
#
|
|
105
|
+
# Messages
|
|
106
|
+
#
|
|
107
|
+
# :messages => {..}
|
|
108
|
+
# Setting :messages hash replaces standard error messages
|
|
109
|
+
# with custom ones. For instance:
|
|
110
|
+
# :messages => {:presence => "Field is required",
|
|
111
|
+
# :format => "Field has invalid format"}
|
|
112
|
+
# Hash keys are: :presence, :format, :length, :is_unique,
|
|
113
|
+
# :is_number, :is_primitive
|
|
114
|
+
#
|
|
115
|
+
# :message => "Some message"
|
|
116
|
+
# It is just shortcut if only one validation option is set
|
|
117
|
+
#
|
|
118
|
+
# @api private
|
|
119
|
+
def self.rules_for_property(property)
|
|
120
|
+
rule_definitions = []
|
|
121
|
+
|
|
122
|
+
# all inferred rules should not be skipped when the value is nil
|
|
123
|
+
# (aside from Rule::Presence/Rule::Absence)
|
|
124
|
+
opts = { :allow_nil => true }
|
|
125
|
+
|
|
126
|
+
if property.options.key?(:validates)
|
|
127
|
+
opts[:context] = property.options[:validates]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
rule_definitions << infer_presence( property, opts.dup)
|
|
131
|
+
rule_definitions << infer_length( property, opts.dup)
|
|
132
|
+
rule_definitions << infer_format( property, opts.dup)
|
|
133
|
+
rule_definitions << infer_uniqueness(property, opts.dup)
|
|
134
|
+
rule_definitions << infer_within( property, opts.dup)
|
|
135
|
+
rule_definitions << infer_type( property, opts.dup)
|
|
136
|
+
|
|
137
|
+
rule_definitions.compact
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
# @api private
|
|
143
|
+
def self.infer_presence(property, options)
|
|
144
|
+
return if property.allow_blank? || property.serial?
|
|
145
|
+
|
|
146
|
+
validation_options = options_with_message(options, property, :presence)
|
|
147
|
+
|
|
148
|
+
[Rule::Presence, property.name, validation_options]
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# @api private
|
|
152
|
+
def self.infer_length(property, options)
|
|
153
|
+
# TODO: return unless property.primitive <= String (?)
|
|
154
|
+
return unless (property.kind_of?(Property::String) ||
|
|
155
|
+
property.kind_of?(Property::Text))
|
|
156
|
+
length = property.options.fetch(:length, Property::String.length)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
if length.is_a?(Range)
|
|
160
|
+
if length.last == Infinity
|
|
161
|
+
raise ArgumentError, "Infinity is not a valid upper bound for a length range"
|
|
162
|
+
end
|
|
163
|
+
options[:within] = length
|
|
164
|
+
else
|
|
165
|
+
options[:maximum] = length
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
validation_options = options_with_message(options, property, :length)
|
|
169
|
+
|
|
170
|
+
[Rule::Length, property.name, validation_options]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# @api private
|
|
174
|
+
def self.infer_format(property, options)
|
|
175
|
+
return unless property.options.key?(:format)
|
|
176
|
+
|
|
177
|
+
options[:with] = property.options[:format]
|
|
178
|
+
|
|
179
|
+
validation_options = options_with_message(options, property, :format)
|
|
180
|
+
|
|
181
|
+
[Rule::Format, property.name, validation_options]
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# @api private
|
|
185
|
+
def self.infer_uniqueness(property, options)
|
|
186
|
+
return unless property.options.key?(:unique)
|
|
187
|
+
|
|
188
|
+
case value = property.options[:unique]
|
|
189
|
+
when Array, Symbol
|
|
190
|
+
# TODO: fix this to behave like :unique_index
|
|
191
|
+
options[:scope] = Array(value)
|
|
192
|
+
|
|
193
|
+
validation_options = options_with_message(options, property, :is_unique)
|
|
194
|
+
[Rule::Uniqueness, property.name, validation_options]
|
|
195
|
+
when TrueClass
|
|
196
|
+
validation_options = options_with_message(options, property, :is_unique)
|
|
197
|
+
[Rule::Uniqueness, property.name, validation_options]
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# @api private
|
|
202
|
+
def self.infer_within(property, options)
|
|
203
|
+
return unless property.options.key?(:set)
|
|
204
|
+
|
|
205
|
+
options[:set] = property.options[:set]
|
|
206
|
+
|
|
207
|
+
validation_options = options_with_message(options, property, :within)
|
|
208
|
+
[Rule::Within, property.name, validation_options]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# @api private
|
|
212
|
+
def self.infer_type(property, options)
|
|
213
|
+
return if property.respond_to?(:custom?) && property.custom?
|
|
214
|
+
|
|
215
|
+
if property.kind_of?(Property::Numeric)
|
|
216
|
+
options[:gte] = property.min if property.min
|
|
217
|
+
options[:lte] = property.max if property.max
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
if Integer == property.load_as
|
|
221
|
+
options[:integer_only] = true
|
|
222
|
+
|
|
223
|
+
validation_options = options_with_message(options, property, :is_number)
|
|
224
|
+
[Rule::Numericalness, property.name, validation_options]
|
|
225
|
+
elsif (BigDecimal == property.load_as ||
|
|
226
|
+
Float == property.load_as)
|
|
227
|
+
options[:precision] = property.precision
|
|
228
|
+
options[:scale] = property.scale
|
|
229
|
+
|
|
230
|
+
validation_options = options_with_message(options, property, :is_number)
|
|
231
|
+
[Rule::Numericalness, property.name, validation_options]
|
|
232
|
+
else
|
|
233
|
+
# We only need this in the case we don't already
|
|
234
|
+
# have a numeric validator, because otherwise
|
|
235
|
+
# it will cause duplicate validation errors
|
|
236
|
+
validation_options = options_with_message(options, property, :is_primitive)
|
|
237
|
+
[Rule::PrimitiveType, property.name, validation_options]
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# TODO: eliminate this;
|
|
242
|
+
# mutating one arg based on a non-obvious interaction of the other two...
|
|
243
|
+
# well, it makes my skin crawl.
|
|
244
|
+
#
|
|
245
|
+
# @api private
|
|
246
|
+
def self.options_with_message(base_options, property, validator_name)
|
|
247
|
+
options = base_options.clone
|
|
248
|
+
opts = property.options
|
|
249
|
+
|
|
250
|
+
if opts.key?(:messages)
|
|
251
|
+
options[:message] = opts[:messages][validator_name]
|
|
252
|
+
elsif opts.key?(:message)
|
|
253
|
+
options[:message] = opts[:message]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
options
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
end # module Inferred
|
|
260
|
+
end # module Validation
|
|
261
|
+
|
|
262
|
+
Model.append_extensions Validation::Inferred
|
|
263
|
+
|
|
264
|
+
end # module DataMapper
|
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'data_mapper/validation/rule'
|
|
4
|
+
|
|
5
|
+
require 'data_mapper/validation/rule/absence'
|
|
6
|
+
require 'data_mapper/validation/rule/acceptance'
|
|
7
|
+
require 'data_mapper/validation/rule/block'
|
|
8
|
+
require 'data_mapper/validation/rule/confirmation'
|
|
9
|
+
require 'data_mapper/validation/rule/format'
|
|
10
|
+
require 'data_mapper/validation/rule/length'
|
|
11
|
+
require 'data_mapper/validation/rule/method'
|
|
12
|
+
require 'data_mapper/validation/rule/numericalness'
|
|
13
|
+
require 'data_mapper/validation/rule/presence'
|
|
14
|
+
require 'data_mapper/validation/rule/primitive_type'
|
|
15
|
+
require 'data_mapper/validation/rule/uniqueness'
|
|
16
|
+
require 'data_mapper/validation/rule/within'
|
|
17
|
+
|
|
18
|
+
module DataMapper
|
|
19
|
+
module Validation
|
|
20
|
+
module Macros
|
|
21
|
+
def self.extract_options(arguments)
|
|
22
|
+
arguments.last.kind_of?(Hash) ? arguments.pop : {}
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Validates that the specified attribute is "blank" via the
|
|
26
|
+
# attribute's #blank? method.
|
|
27
|
+
#
|
|
28
|
+
# @note
|
|
29
|
+
# dm-core's support lib adds the #blank? method to many classes,
|
|
30
|
+
# @see lib/dm-core/support/blank.rb (dm-core) for more information.
|
|
31
|
+
#
|
|
32
|
+
# @example [Usage]
|
|
33
|
+
# require 'dm-validations'
|
|
34
|
+
#
|
|
35
|
+
# class Page
|
|
36
|
+
# include DataMapper::Resource
|
|
37
|
+
#
|
|
38
|
+
# property :unwanted_attribute, String
|
|
39
|
+
# property :another_unwanted, String
|
|
40
|
+
# property :yet_again, String
|
|
41
|
+
#
|
|
42
|
+
# validates_absence_of :unwanted_attribute
|
|
43
|
+
# validates_absence_of :another_unwanted, :yet_again
|
|
44
|
+
#
|
|
45
|
+
# # a call to #validate will return false unless
|
|
46
|
+
# # all three attributes are blank
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
def validates_absence_of(*attribute_names)
|
|
50
|
+
options = Macros.extract_options(attribute_names)
|
|
51
|
+
validation_rules.add(Rule::Absence, attribute_names, options)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Validates that the attributes's value is in the set of accepted
|
|
55
|
+
# values.
|
|
56
|
+
#
|
|
57
|
+
# @option [Boolean] :allow_nil (true)
|
|
58
|
+
# true if nil is allowed, false if not allowed.
|
|
59
|
+
#
|
|
60
|
+
# @option [Array] :accept (["1", 1, "true", true, "t"])
|
|
61
|
+
# A list of accepted values.
|
|
62
|
+
#
|
|
63
|
+
# @example Usage
|
|
64
|
+
# require 'dm-validations'
|
|
65
|
+
#
|
|
66
|
+
# class Page
|
|
67
|
+
# include DataMapper::Resource
|
|
68
|
+
#
|
|
69
|
+
# property :license_agreement_accepted, String
|
|
70
|
+
# property :terms_accepted, String
|
|
71
|
+
# validates_acceptance_of :license_agreement, :accept => "1"
|
|
72
|
+
# validates_acceptance_of :terms_accepted, :allow_nil => false
|
|
73
|
+
#
|
|
74
|
+
# # a call to valid? will return false unless:
|
|
75
|
+
# # license_agreement is nil or "1"
|
|
76
|
+
# # and
|
|
77
|
+
# # terms_accepted is one of ["1", 1, "true", true, "t"]
|
|
78
|
+
#
|
|
79
|
+
def validates_acceptance_of(*attribute_names)
|
|
80
|
+
options = Macros.extract_options(attribute_names)
|
|
81
|
+
validation_rules.add(Rule::Acceptance, attribute_names, options)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Validate using the given block. The block given needs to return:
|
|
85
|
+
# [result::<Boolean>, Error Message::<String>]
|
|
86
|
+
#
|
|
87
|
+
# @example [Usage]
|
|
88
|
+
# require 'dm-validations'
|
|
89
|
+
#
|
|
90
|
+
# class Page
|
|
91
|
+
# include DataMapper::Resource
|
|
92
|
+
#
|
|
93
|
+
# property :zip_code, String
|
|
94
|
+
#
|
|
95
|
+
# validates_with_block do
|
|
96
|
+
# if @zip_code == "94301"
|
|
97
|
+
# true
|
|
98
|
+
# else
|
|
99
|
+
# [false, "You're in the wrong zip code"]
|
|
100
|
+
# end
|
|
101
|
+
# end
|
|
102
|
+
#
|
|
103
|
+
# # A call to valid? will return false and
|
|
104
|
+
# # populate the object's errors with "You're in the
|
|
105
|
+
# # wrong zip code" unless zip_code == "94301"
|
|
106
|
+
#
|
|
107
|
+
# # You can also specify field:
|
|
108
|
+
#
|
|
109
|
+
# validates_with_block :zip_code do
|
|
110
|
+
# if @zip_code == "94301"
|
|
111
|
+
# true
|
|
112
|
+
# else
|
|
113
|
+
# [false, "You're in the wrong zip code"]
|
|
114
|
+
# end
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
117
|
+
# # it will add returned error message to :zip_code field
|
|
118
|
+
#
|
|
119
|
+
def validates_with_block(*attribute_names, &block)
|
|
120
|
+
unless block_given?
|
|
121
|
+
raise ArgumentError, 'You need to pass a block to validates_with_block'
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
options = Macros.extract_options(attribute_names)
|
|
125
|
+
validation_rules.add(Rule::Block, attribute_names, options, &block)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Validates that the given attribute is confirmed by another
|
|
129
|
+
# attribute. A common use case scenario is when you require a user to
|
|
130
|
+
# confirm their password, for which you use both password and
|
|
131
|
+
# password_confirmation attributes.
|
|
132
|
+
#
|
|
133
|
+
# @option [Boolean] :allow_nil (true)
|
|
134
|
+
# true or false.
|
|
135
|
+
#
|
|
136
|
+
# @option [Boolean] :allow_blank (true)
|
|
137
|
+
# true or false.
|
|
138
|
+
#
|
|
139
|
+
# @option [Symbol] :confirm (firstattr_confirmation)
|
|
140
|
+
# The attribute that you want to validate against.
|
|
141
|
+
#
|
|
142
|
+
# @example Usage
|
|
143
|
+
# require 'dm-validations'
|
|
144
|
+
#
|
|
145
|
+
# class Page
|
|
146
|
+
# include DataMapper::Resource
|
|
147
|
+
#
|
|
148
|
+
# property :password, String
|
|
149
|
+
# property :email, String
|
|
150
|
+
# attr_accessor :password_confirmation
|
|
151
|
+
# attr_accessor :email_repeated
|
|
152
|
+
#
|
|
153
|
+
# validates_confirmation_of :password
|
|
154
|
+
# validates_confirmation_of :email, :confirm => :email_repeated
|
|
155
|
+
#
|
|
156
|
+
# # a call to valid? will return false unless:
|
|
157
|
+
# # password == password_confirmation
|
|
158
|
+
# # and
|
|
159
|
+
# # email == email_repeated
|
|
160
|
+
#
|
|
161
|
+
def validates_confirmation_of(*attribute_names)
|
|
162
|
+
options = Macros.extract_options(attribute_names)
|
|
163
|
+
validation_rules.add(Rule::Confirmation, attribute_names, options)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Validates that the attribute is in the specified format. You may
|
|
167
|
+
# use the :as (or :with, it's an alias) option to specify the
|
|
168
|
+
# pre-defined format that you want to validate against. You may also
|
|
169
|
+
# specify your own format via a Proc or Regexp passed to the the :as
|
|
170
|
+
# or :with options.
|
|
171
|
+
#
|
|
172
|
+
# @option [Boolean] :allow_nil (true)
|
|
173
|
+
# true or false.
|
|
174
|
+
#
|
|
175
|
+
# @option [Boolean] :allow_blank (true)
|
|
176
|
+
# true or false.
|
|
177
|
+
#
|
|
178
|
+
# @option [Format, Proc, Regexp] :as
|
|
179
|
+
# The pre-defined format, Proc or Regexp to validate against.
|
|
180
|
+
#
|
|
181
|
+
# @option [Format, Proc, Regexp] :with
|
|
182
|
+
# An alias for :as.
|
|
183
|
+
#
|
|
184
|
+
# :email_address (format is specified in DataMapper::Validation::Format::Email - note that unicode emails will *not* be matched under MRI1.8.7)
|
|
185
|
+
# :url (format is specified in DataMapper::Validation::Format::Url)
|
|
186
|
+
#
|
|
187
|
+
# @example Usage
|
|
188
|
+
# require 'dm-validations'
|
|
189
|
+
#
|
|
190
|
+
# class Page
|
|
191
|
+
# include DataMapper::Resource
|
|
192
|
+
#
|
|
193
|
+
# property :email, String
|
|
194
|
+
# property :zip_code, String
|
|
195
|
+
#
|
|
196
|
+
# validates_format_of :email, :as => :email_address
|
|
197
|
+
# validates_format_of :zip_code, :with => /^\d{5}$/
|
|
198
|
+
#
|
|
199
|
+
# # a call to valid? will return false unless:
|
|
200
|
+
# # email is formatted like an email address
|
|
201
|
+
# # and
|
|
202
|
+
# # zip_code is a string of 5 digits
|
|
203
|
+
#
|
|
204
|
+
def validates_format_of(*attribute_names)
|
|
205
|
+
options = Macros.extract_options(attribute_names)
|
|
206
|
+
validation_rules.add(Rule::Format, attribute_names, options)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Validates that the length of the attribute is equal to, less than,
|
|
210
|
+
# greater than or within a certain range (depending upon the options
|
|
211
|
+
# you specify).
|
|
212
|
+
#
|
|
213
|
+
# @option [Boolean] :allow_nil (true)
|
|
214
|
+
# true or false.
|
|
215
|
+
#
|
|
216
|
+
# @option [Boolean] :allow_blank (true)
|
|
217
|
+
# true or false.
|
|
218
|
+
#
|
|
219
|
+
# @option [Boolean] :minimum
|
|
220
|
+
# Ensures that the attribute's length is greater than or equal to
|
|
221
|
+
# the supplied value.
|
|
222
|
+
#
|
|
223
|
+
# @option [Boolean] :min
|
|
224
|
+
# Alias for :minimum.
|
|
225
|
+
#
|
|
226
|
+
# @option [Boolean] :maximum
|
|
227
|
+
# Ensures the attribute's length is less than or equal to the
|
|
228
|
+
# supplied value.
|
|
229
|
+
#
|
|
230
|
+
# @option [Boolean] :max
|
|
231
|
+
# Alias for :maximum.
|
|
232
|
+
#
|
|
233
|
+
# @option [Boolean] :equals
|
|
234
|
+
# Ensures the attribute's length is equal to the supplied value.
|
|
235
|
+
#
|
|
236
|
+
# @option [Boolean] :is
|
|
237
|
+
# Alias for :equals.
|
|
238
|
+
#
|
|
239
|
+
# @option [Range] :in
|
|
240
|
+
# Given a Range, ensures that the attributes length is include?'ed
|
|
241
|
+
# in the Range.
|
|
242
|
+
#
|
|
243
|
+
# @option [Range] :within
|
|
244
|
+
# Alias for :in.
|
|
245
|
+
#
|
|
246
|
+
# @example Usage
|
|
247
|
+
# require 'dm-validations'
|
|
248
|
+
#
|
|
249
|
+
# class Page
|
|
250
|
+
# include DataMapper::Resource
|
|
251
|
+
#
|
|
252
|
+
# property high, Integer
|
|
253
|
+
# property low, Integer
|
|
254
|
+
# property just_right, Integer
|
|
255
|
+
#
|
|
256
|
+
# validates_length_of :high, :min => 100000000000
|
|
257
|
+
# validates_length_of :low, :equals => 0
|
|
258
|
+
# validates_length_of :just_right, :within => 1..10
|
|
259
|
+
#
|
|
260
|
+
# # a call to valid? will return false unless:
|
|
261
|
+
# # high is greater than or equal to 100000000000
|
|
262
|
+
# # low is equal to 0
|
|
263
|
+
# # just_right is between 1 and 10 (inclusive of both 1 and 10)
|
|
264
|
+
#
|
|
265
|
+
def validates_length_of(*attribute_names)
|
|
266
|
+
options = Macros.extract_options(attribute_names)
|
|
267
|
+
validation_rules.add(Rule::Length, attribute_names, options)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Validate using method called on validated object. The method must
|
|
271
|
+
# to return either true, or a pair of [false, error message string],
|
|
272
|
+
# and is specified as a symbol passed with :method option.
|
|
273
|
+
#
|
|
274
|
+
# This validator does support multiple attribute_names being specified at a
|
|
275
|
+
# time, but we encourage you to use it with one property/method at a
|
|
276
|
+
# time.
|
|
277
|
+
#
|
|
278
|
+
# Real world experience shows that method validation is often useful
|
|
279
|
+
# when attribute needs to be virtual and not a property name.
|
|
280
|
+
#
|
|
281
|
+
# @example Usage
|
|
282
|
+
# require 'dm-validations'
|
|
283
|
+
#
|
|
284
|
+
# class Page
|
|
285
|
+
# include DataMapper::Resource
|
|
286
|
+
#
|
|
287
|
+
# property :zip_code, String
|
|
288
|
+
#
|
|
289
|
+
# validates_with_method :zip_code,
|
|
290
|
+
# :method => :in_the_right_location?
|
|
291
|
+
#
|
|
292
|
+
# def in_the_right_location?
|
|
293
|
+
# if @zip_code == "94301"
|
|
294
|
+
# return true
|
|
295
|
+
# else
|
|
296
|
+
# return [false, "You're in the wrong zip code"]
|
|
297
|
+
# end
|
|
298
|
+
# end
|
|
299
|
+
#
|
|
300
|
+
# # A call to valid? will return false and
|
|
301
|
+
# # populate the object's errors with "You're in the
|
|
302
|
+
# # wrong zip code" unless zip_code == "94301"
|
|
303
|
+
# end
|
|
304
|
+
def validates_with_method(*attribute_names)
|
|
305
|
+
options = Macros.extract_options(attribute_names)
|
|
306
|
+
validation_rules.add(Rule::Method, attribute_names, options)
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Validate whether a field is numeric.
|
|
310
|
+
#
|
|
311
|
+
# @option [Boolean] :allow_nil
|
|
312
|
+
# true if number can be nil, false if not.
|
|
313
|
+
#
|
|
314
|
+
# @option [Boolean] :allow_blank
|
|
315
|
+
# true if number can be blank, false if not.
|
|
316
|
+
#
|
|
317
|
+
# @option [String] :message
|
|
318
|
+
# Custom error message, also can be a callable object that takes
|
|
319
|
+
# an object (for pure Ruby objects) or object and property
|
|
320
|
+
# (for DM resources).
|
|
321
|
+
#
|
|
322
|
+
# @option [Numeric] :precision
|
|
323
|
+
# Required precision of a value.
|
|
324
|
+
#
|
|
325
|
+
# @option [Numeric] :scale
|
|
326
|
+
# Required scale of a value.
|
|
327
|
+
#
|
|
328
|
+
# @option [Numeric] :gte
|
|
329
|
+
# 'Greater than or equal to' requirement.
|
|
330
|
+
#
|
|
331
|
+
# @option [Numeric] :lte
|
|
332
|
+
# 'Less than or equal to' requirement.
|
|
333
|
+
#
|
|
334
|
+
# @option [Numeric] :lt
|
|
335
|
+
# 'Less than' requirement.
|
|
336
|
+
#
|
|
337
|
+
# @option [Numeric] :gt
|
|
338
|
+
# 'Greater than' requirement.
|
|
339
|
+
#
|
|
340
|
+
# @option [Numeric] :eq
|
|
341
|
+
# 'Equal' requirement.
|
|
342
|
+
#
|
|
343
|
+
# @option [Numeric] :ne
|
|
344
|
+
# 'Not equal' requirement.
|
|
345
|
+
#
|
|
346
|
+
# @option [Boolean] :integer_only
|
|
347
|
+
# Use to restrict allowed values to integers.
|
|
348
|
+
#
|
|
349
|
+
def validates_numericalness_of(*attribute_names)
|
|
350
|
+
options = Macros.extract_options(attribute_names)
|
|
351
|
+
validation_rules.add(Rule::Numericalness, attribute_names, options)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Validates that the specified attribute is present.
|
|
355
|
+
#
|
|
356
|
+
# For most property types "being present" is the same as being "not
|
|
357
|
+
# blank" as determined by the attribute's #blank? method. However, in
|
|
358
|
+
# the case of Boolean, "being present" means not nil; i.e. true or
|
|
359
|
+
# false.
|
|
360
|
+
#
|
|
361
|
+
# @note
|
|
362
|
+
# dm-core's support lib adds the blank? method to many classes,
|
|
363
|
+
#
|
|
364
|
+
# @see lib/dm-core/support/blank.rb (dm-core) for more information.
|
|
365
|
+
#
|
|
366
|
+
# @example Usage
|
|
367
|
+
# require 'dm-validations'
|
|
368
|
+
#
|
|
369
|
+
# class Page
|
|
370
|
+
# include DataMapper::Resource
|
|
371
|
+
#
|
|
372
|
+
# property :required_attribute, String
|
|
373
|
+
# property :another_required, String
|
|
374
|
+
# property :yet_again, String
|
|
375
|
+
#
|
|
376
|
+
# validates_presence_of :required_attribute
|
|
377
|
+
# validates_presence_of :another_required, :yet_again
|
|
378
|
+
#
|
|
379
|
+
# # a call to valid? will return false unless
|
|
380
|
+
# # all three attributes are !blank?
|
|
381
|
+
# end
|
|
382
|
+
def validates_presence_of(*attribute_names)
|
|
383
|
+
options = attribute_names.last.kind_of?(Hash) ? attribute_names.pop : {}
|
|
384
|
+
validation_rules.add(Rule::Presence, attribute_names, options)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Validates that the specified attribute is of the correct primitive
|
|
388
|
+
# type.
|
|
389
|
+
#
|
|
390
|
+
# @example Usage
|
|
391
|
+
# require 'dm-validations'
|
|
392
|
+
#
|
|
393
|
+
# class Person
|
|
394
|
+
# include DataMapper::Resource
|
|
395
|
+
#
|
|
396
|
+
# property :birth_date, Date
|
|
397
|
+
#
|
|
398
|
+
# validates_primitive_type_of :birth_date
|
|
399
|
+
#
|
|
400
|
+
# # a call to valid? will return false unless
|
|
401
|
+
# # the birth_date is something that can be properly
|
|
402
|
+
# # casted into a Date object.
|
|
403
|
+
# end
|
|
404
|
+
def validates_primitive_type_of(*attribute_names)
|
|
405
|
+
options = Macros.extract_options(attribute_names)
|
|
406
|
+
validation_rules.add(Rule::PrimitiveType, attribute_names, options)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
# Validate the uniqueness of a field
|
|
410
|
+
#
|
|
411
|
+
# TODO: YARDoc for this method
|
|
412
|
+
def validates_uniqueness_of(*attribute_names)
|
|
413
|
+
options = Macros.extract_options(attribute_names)
|
|
414
|
+
validation_rules.add(Rule::Uniqueness, attribute_names, options)
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Validates that the value of a field is within a range/set.
|
|
418
|
+
#
|
|
419
|
+
# This validation is defined by passing a field along with a :set
|
|
420
|
+
# parameter. The :set can be a Range or any object which responds
|
|
421
|
+
# to the #include? method (an array, for example).
|
|
422
|
+
#
|
|
423
|
+
# @example Usage
|
|
424
|
+
# require 'dm-validations'
|
|
425
|
+
#
|
|
426
|
+
# class Review
|
|
427
|
+
# include DataMapper::Resource
|
|
428
|
+
#
|
|
429
|
+
# STATES = ['new', 'in_progress', 'published', 'archived']
|
|
430
|
+
#
|
|
431
|
+
# property :title, String
|
|
432
|
+
# property :body, String
|
|
433
|
+
# property :review_state, String
|
|
434
|
+
# property :rating, Integer
|
|
435
|
+
#
|
|
436
|
+
# validates_within :review_state, :set => STATES
|
|
437
|
+
# validates_within :rating, :set => 1..5
|
|
438
|
+
#
|
|
439
|
+
# # a call to valid? will return false unless
|
|
440
|
+
# # the two properties conform to their sets
|
|
441
|
+
# end
|
|
442
|
+
def validates_within(*attribute_names)
|
|
443
|
+
options = Macros.extract_options(attribute_names)
|
|
444
|
+
validation_rules.add(Rule::Within, attribute_names, options)
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
end # module Macros
|
|
448
|
+
end # module Validation
|
|
449
|
+
end # module DataMapper
|