strokedb 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTORS +7 -0
- data/CREDITS +13 -0
- data/README +44 -0
- data/bin/sdbc +2 -0
- data/lib/config/config.rb +161 -0
- data/lib/data_structures/inverted_list.rb +297 -0
- data/lib/data_structures/point_query.rb +24 -0
- data/lib/data_structures/skiplist.rb +302 -0
- data/lib/document/associations.rb +107 -0
- data/lib/document/callback.rb +11 -0
- data/lib/document/coercions.rb +57 -0
- data/lib/document/delete.rb +28 -0
- data/lib/document/document.rb +684 -0
- data/lib/document/meta.rb +261 -0
- data/lib/document/slot.rb +199 -0
- data/lib/document/util.rb +27 -0
- data/lib/document/validations.rb +704 -0
- data/lib/document/versions.rb +106 -0
- data/lib/document/virtualize.rb +82 -0
- data/lib/init.rb +57 -0
- data/lib/stores/chainable_storage.rb +57 -0
- data/lib/stores/inverted_list_index/inverted_list_file_storage.rb +56 -0
- data/lib/stores/inverted_list_index/inverted_list_index.rb +49 -0
- data/lib/stores/remote_store.rb +172 -0
- data/lib/stores/skiplist_store/chunk.rb +119 -0
- data/lib/stores/skiplist_store/chunk_storage.rb +21 -0
- data/lib/stores/skiplist_store/file_chunk_storage.rb +44 -0
- data/lib/stores/skiplist_store/memory_chunk_storage.rb +37 -0
- data/lib/stores/skiplist_store/skiplist_store.rb +217 -0
- data/lib/stores/store.rb +5 -0
- data/lib/sync/chain_sync.rb +38 -0
- data/lib/sync/diff.rb +126 -0
- data/lib/sync/lamport_timestamp.rb +81 -0
- data/lib/sync/store_sync.rb +79 -0
- data/lib/sync/stroke_diff/array.rb +102 -0
- data/lib/sync/stroke_diff/default.rb +21 -0
- data/lib/sync/stroke_diff/hash.rb +186 -0
- data/lib/sync/stroke_diff/string.rb +116 -0
- data/lib/sync/stroke_diff/stroke_diff.rb +9 -0
- data/lib/util/blankslate.rb +42 -0
- data/lib/util/ext/blank.rb +50 -0
- data/lib/util/ext/enumerable.rb +36 -0
- data/lib/util/ext/fixnum.rb +16 -0
- data/lib/util/ext/hash.rb +22 -0
- data/lib/util/ext/object.rb +8 -0
- data/lib/util/ext/string.rb +35 -0
- data/lib/util/inflect.rb +217 -0
- data/lib/util/java_util.rb +9 -0
- data/lib/util/lazy_array.rb +54 -0
- data/lib/util/lazy_mapping_array.rb +64 -0
- data/lib/util/lazy_mapping_hash.rb +46 -0
- data/lib/util/serialization.rb +29 -0
- data/lib/util/trigger_partition.rb +136 -0
- data/lib/util/util.rb +38 -0
- data/lib/util/xml.rb +6 -0
- data/lib/view/view.rb +55 -0
- data/script/console +70 -0
- data/strokedb.rb +75 -0
- metadata +148 -0
@@ -0,0 +1,704 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module StrokeDB
|
4
|
+
module Validations
|
5
|
+
ERROR_MESSAGES = {
|
6
|
+
:should_be_present => '#{meta}\'s #{slotname} should be present on #{on}',
|
7
|
+
:invalid_type => '#{meta}\'s #{slotname} should be of type #{validation_type}',
|
8
|
+
:already_exists => 'A document with a #{slotname} of #{slotvalue} already exists',
|
9
|
+
:not_included => 'Value of #{slotname} is not included in the list',
|
10
|
+
:not_excluded => 'Value of #{slotname} is reserved',
|
11
|
+
:invalid_format => 'Value of #{slotname} should match #{slotvalue}',
|
12
|
+
:not_confirmed => '#{meta}\'s #{slotname} doesn\'t match confirmation',
|
13
|
+
:not_accepted => '#{slotname} must be accepted',
|
14
|
+
:wrong_length => '#{slotname} has the wrong length (should be %d characters)',
|
15
|
+
:too_short => '#{slotname} is too short (minimum is %d characters)',
|
16
|
+
:too_long => '#{slotname} is too long (maximum is %d characters)',
|
17
|
+
:invalid => '#{slotname} is invalid',
|
18
|
+
}.freeze unless defined? ERROR_MESSAGES
|
19
|
+
|
20
|
+
# Validates that the specified slot exists in the document. Happens by default on save. Example:
|
21
|
+
#
|
22
|
+
# Person = Meta.new do
|
23
|
+
# validates_presence_of :first_name
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# The first_name slot must be in the document.
|
27
|
+
#
|
28
|
+
# Configuration options:
|
29
|
+
# * <tt>message</tt> - A custom error message (default is: "should be present on ...")
|
30
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
31
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
32
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
33
|
+
# method result or slot should be equal to a true or false value.
|
34
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
35
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
36
|
+
# method result or slot should be equal to a true or false value.
|
37
|
+
def validates_presence_of(slotname, opts={})
|
38
|
+
register_validation("presence_of", slotname, opts, :should_be_present)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Validates that the specified slot value has a specific type. Happens by default on save. Example:
|
42
|
+
#
|
43
|
+
# Person = Meta.new do
|
44
|
+
# validates_type_of :first_name, :as => :string
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# The first_name value for each Person must be unique.
|
48
|
+
#
|
49
|
+
# Configuration options:
|
50
|
+
# * <tt>message</tt> - A custom error message (default is: "document with value already exists")
|
51
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
52
|
+
# * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
|
53
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
54
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
55
|
+
# method result or slot should be equal to a true or false value.
|
56
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
57
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
58
|
+
# method result or slot should be equal to a true or false value.
|
59
|
+
#
|
60
|
+
# === Warning
|
61
|
+
# When the slot doesn't exist, validation gets skipped.
|
62
|
+
def validates_type_of(slotname, opts={})
|
63
|
+
register_validation("type_of", slotname, opts, :invalid_type) do |opts|
|
64
|
+
raise ArgumentError, "validates_type_of requires :as => type" unless type = opts['as']
|
65
|
+
|
66
|
+
{
|
67
|
+
:validation_type => type.to_s.camelize,
|
68
|
+
:allow_nil => !!opts['allow_nil']
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Validates that the specified slot value is unique in the store
|
74
|
+
#
|
75
|
+
# Person = Meta.new do
|
76
|
+
# validates_uniqueness_of :first_name
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# The first_name slot must be in the document.
|
80
|
+
#
|
81
|
+
# Configuration options:
|
82
|
+
# * <tt>message</tt> - A custom error message (default is: "A document with a ... of ... already exists")
|
83
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
84
|
+
# * <tt>case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (true by default). NOT YET IMPLEMENTED
|
85
|
+
# * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
|
86
|
+
# * <tt>allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is: false)
|
87
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
88
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
89
|
+
# method result or slot should be equal to a true or false value.
|
90
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
91
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
92
|
+
# method result or slot should be equal to a true or false value.
|
93
|
+
def validates_uniqueness_of(slotname, opts={})
|
94
|
+
register_validation("uniqueness_of", slotname, opts, :already_exists) do |opts|
|
95
|
+
{ :allow_nil => !!opts['allow_nil'], :allow_blank => !!opts['allow_blank'] }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Validates whether the value of the specified slot is available in a particular enumerable object.
|
100
|
+
#
|
101
|
+
# Person = Meta.new do
|
102
|
+
# validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!"
|
103
|
+
# validates_inclusion_of :age, :in => 0..99
|
104
|
+
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => 'extension #{slotvalue} is not included in the list'
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# Configuration options:
|
108
|
+
# * <tt>in</tt> - An enumerable object of available items
|
109
|
+
# * <tt>message</tt> - Specifies a customer error message (default is: "is
|
110
|
+
# not included in the list")
|
111
|
+
# * <tt>allow_nil</tt> - If set to true, skips this validation if the slot is null (default is: false)
|
112
|
+
# * <tt>allow_blank</tt> - If set to true, skips this validation if the slot is blank (default is: false)
|
113
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
114
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
115
|
+
# method result or slot should be equal to a true or false value.
|
116
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
117
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
118
|
+
# method result or slot should be equal to a true or false value.
|
119
|
+
def validates_inclusion_of(slotname, opts={})
|
120
|
+
register_validation("inclusion_of", slotname, opts, :not_included) do |opts|
|
121
|
+
raise ArgumentError, "validates_inclusion_of requires :in set" unless opts['in']
|
122
|
+
raise ArgumentError, "object must respond to the method include?" unless opts['in'].respond_to? :include?
|
123
|
+
|
124
|
+
{
|
125
|
+
:in => opts['in'],
|
126
|
+
:allow_nil => !!opts['allow_nil'],
|
127
|
+
:allow_blank => !!opts['allow_blank']
|
128
|
+
}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Validates that the value of the specified slot is not in a particular enumerable object.
|
133
|
+
#
|
134
|
+
# Person = Meta.new do
|
135
|
+
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
|
136
|
+
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
|
137
|
+
# validates_exclusion_of :format, :in => %w( mov avi ), :message => 'extension #{slotvalue} is not allowed'
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# Configuration options:
|
141
|
+
# * <tt>in</tt> - An enumerable object of items that the value shouldn't be part of
|
142
|
+
# * <tt>message</tt> - Specifies a customer error message (default is: "is reserved")
|
143
|
+
# * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
|
144
|
+
# * <tt>allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is: false)
|
145
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
146
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
147
|
+
# method result or slot should be equal to a true or false value.
|
148
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
149
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
150
|
+
# method result or slot should be equal to a true or false value.
|
151
|
+
def validates_exclusion_of(slotname, opts={})
|
152
|
+
register_validation("exclusion_of", slotname, opts, :not_excluded) do |opts|
|
153
|
+
raise ArgumentError, "validates_exclusion_of requires :in set" unless opts['in']
|
154
|
+
raise ArgumentError, "object must respond to the method include?" unless opts['in'].respond_to? :include?
|
155
|
+
|
156
|
+
{
|
157
|
+
:in => opts['in'],
|
158
|
+
:allow_nil => !!opts['allow_nil'],
|
159
|
+
:allow_blank => !!opts['allow_blank']
|
160
|
+
}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Validates whether the value of the specified attribute is numeric by trying to convert it to
|
165
|
+
# a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression
|
166
|
+
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true).
|
167
|
+
#
|
168
|
+
# Item = Meta.new do
|
169
|
+
# validates_numericality_of :price
|
170
|
+
# end
|
171
|
+
#
|
172
|
+
# * <tt>message</tt> - A custom error message (default is: "is not a number")
|
173
|
+
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
174
|
+
# * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
|
175
|
+
# * <tt>allow_nil</tt> Skip validation if attribute is nil (default is
|
176
|
+
# false). Notice that for fixnum and float columns empty strings are converted to nil
|
177
|
+
# * <tt>greater_than</tt> Specifies the value must be greater than the supplied value
|
178
|
+
# * <tt>greater_than_or_equal_to</tt> Specifies the value must be greater than or equal the supplied value
|
179
|
+
# * <tt>equal_to</tt> Specifies the value must be equal to the supplied value
|
180
|
+
# * <tt>less_than</tt> Specifies the value must be less than the supplied value
|
181
|
+
# * <tt>less_than_or_equal_to</tt> Specifies the value must be less than or equal the supplied value
|
182
|
+
# * <tt>odd</tt> Specifies the value must be an odd number
|
183
|
+
# * <tt>even</tt> Specifies the value must be an even number
|
184
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
185
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
186
|
+
# method result or slot should be equal to a true or false value.
|
187
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
188
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
189
|
+
# method result or slot should be equal to a true or false value.
|
190
|
+
NUMERICALITY_CHECKS = { 'greater_than' => :>, 'greater_than_or_equal_to' => :>=,
|
191
|
+
'equal_to' => :==, 'less_than' => :<, 'less_than_or_equal_to' => :<=,
|
192
|
+
'odd' => :odd?, 'even' => :even? }.freeze
|
193
|
+
|
194
|
+
def validates_numericality_of(slotname, opts={})
|
195
|
+
register_validation("numericality_of", slotname, opts, nil) do |opts|
|
196
|
+
numeric_checks = opts.reject { |key, val| !NUMERICALITY_CHECKS.include? key }
|
197
|
+
|
198
|
+
%w(odd even).each do |o|
|
199
|
+
raise ArgumentError, ":#{o} must be set to true if set at all" if opts.include?(o) && opts[o] != true
|
200
|
+
end
|
201
|
+
|
202
|
+
(numeric_checks.keys - %w(odd even)).each do |option|
|
203
|
+
raise ArgumentError, "#{option} must be a number" unless opts[option].is_a? Numeric
|
204
|
+
end
|
205
|
+
|
206
|
+
{
|
207
|
+
:only_integer => opts['only_integer'],
|
208
|
+
:numeric_checks => numeric_checks,
|
209
|
+
:allow_nil => !!opts['allow_nil']
|
210
|
+
}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Validates whether the value of the specified attribute is of the correct
|
215
|
+
# form by matching it against the regular expression provided.
|
216
|
+
#
|
217
|
+
# Person = Meta.new do
|
218
|
+
# validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
|
219
|
+
# end
|
220
|
+
#
|
221
|
+
# Note: use \A and \Z to match the start and end of the string, ^ and $ match the start/end of a line.
|
222
|
+
#
|
223
|
+
# A regular expression must be provided or else an exception will be
|
224
|
+
# raised.
|
225
|
+
#
|
226
|
+
# Configuration options:
|
227
|
+
# * <tt>message</tt> - A custom error message (default is: "is invalid")
|
228
|
+
# * <tt>with</tt> - The regular expression used to validate the format with (note: must be supplied!)
|
229
|
+
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
230
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
231
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
232
|
+
# method result or slot should be equal to a true or false value.
|
233
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
234
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
235
|
+
# method result or slot should be equal to a true or false value.
|
236
|
+
def validates_format_of(slotname, opts={})
|
237
|
+
register_validation("format_of", slotname, opts, :invalid_format) do |opts|
|
238
|
+
unless regexp = opts['with'].is_a?(Regexp)
|
239
|
+
raise ArgumentError, "validates_format_of requires :with => regexp"
|
240
|
+
end
|
241
|
+
{ :with => opts['with'] }
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Encapsulates the pattern of wanting to validate a password or email
|
246
|
+
# address field with a confirmation. Example:
|
247
|
+
#
|
248
|
+
# Model:
|
249
|
+
# Person = Meta.new do
|
250
|
+
# validates_confirmation_of :password
|
251
|
+
# validates_confirmation_of :email_address, :message => "should match confirmation"
|
252
|
+
# end
|
253
|
+
#
|
254
|
+
# View:
|
255
|
+
# <%= password_field "person", "password" %>
|
256
|
+
# <%= password_field "person", "password_confirmation" %>
|
257
|
+
#
|
258
|
+
# The added +password_confirmation+ slot is virtual; it exists only as
|
259
|
+
# an in-memory slot for validating the password. To achieve this, the
|
260
|
+
# validation adds accessors to the model for the confirmation slot.
|
261
|
+
# NOTE: This check is performed only if +password_confirmation+ is not nil,
|
262
|
+
# and by default only on save. To require confirmation, make sure to add a
|
263
|
+
# presence check for the confirmation attribute:
|
264
|
+
#
|
265
|
+
# validates_presence_of :password_confirmation, :if => :password_changed?
|
266
|
+
#
|
267
|
+
# Configuration options:
|
268
|
+
# * <tt>message</tt> - A custom error message (default is: "doesn't match confirmation")
|
269
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
270
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
271
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
272
|
+
# method result or slot should be equal to a true or false value.
|
273
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
274
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
275
|
+
# method result or slot should be equal to a true or false value.
|
276
|
+
def validates_confirmation_of(slotname, opts = {})
|
277
|
+
register_validation("confirmation_of", slotname, opts, :not_confirmed)
|
278
|
+
|
279
|
+
virtualizes(slotname.to_s + "_confirmation")
|
280
|
+
end
|
281
|
+
|
282
|
+
# Encapsulates the pattern of wanting to validate the acceptance of a terms
|
283
|
+
# of service check box (or similar agreement). Example:
|
284
|
+
#
|
285
|
+
# Person = Meta.new do
|
286
|
+
# validates_acceptance_of :terms_of_service
|
287
|
+
# validates_acceptance_of :eula, :message => "must be abided"
|
288
|
+
# end
|
289
|
+
#
|
290
|
+
# The terms_of_service and eula slots are virtualized. This check is
|
291
|
+
# performed only if terms_of_service is not nil and by default on save.
|
292
|
+
#
|
293
|
+
# Configuration options:
|
294
|
+
# * <tt>message</tt> - A custom error message (default is: "must be accepted")
|
295
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
296
|
+
# * <tt>allow_nil</tt> - Skip validation if attribute is nil. (default is true)
|
297
|
+
# * <tt>accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
|
298
|
+
# makes it easy to relate to an HTML checkbox. This should be set to 'true' if you are validating a database
|
299
|
+
# column, since the attribute is typecast from "1" to <tt>true</tt> before validation.
|
300
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
301
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
302
|
+
# method result or slot should be equal to a true or false value.
|
303
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
304
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
305
|
+
# method result or slot should be equal to a true or false value.
|
306
|
+
def validates_acceptance_of(slotname, opts = {})
|
307
|
+
register_validation("acceptance_of", slotname, opts, :not_accepted) do |opts|
|
308
|
+
allow_nil = opts['allow_nil'].nil? ? true : !!opts['allow_nil']
|
309
|
+
accept = opts['accept'] || "1"
|
310
|
+
|
311
|
+
{ :allow_nil => allow_nil, :accept => accept }
|
312
|
+
end
|
313
|
+
|
314
|
+
virtualizes slotname.to_s
|
315
|
+
end
|
316
|
+
|
317
|
+
# Validates that the specified slot matches the length restrictions
|
318
|
+
# supplied. Only one option can be used at a time:
|
319
|
+
#
|
320
|
+
# Person = Meta.new do
|
321
|
+
# validates_length_of :first_name, :maximum=>30
|
322
|
+
# validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
|
323
|
+
# validates_length_of :fax, :in => 7..32, :allow_nil => true
|
324
|
+
# validates_length_of :phone, :in => 7..32, :allow_blank => true
|
325
|
+
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
|
326
|
+
# validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
|
327
|
+
# validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
|
328
|
+
# end
|
329
|
+
#
|
330
|
+
# Configuration options:
|
331
|
+
# * <tt>minimum</tt> - The minimum size of the attribute
|
332
|
+
# * <tt>maximum</tt> - The maximum size of the attribute
|
333
|
+
# * <tt>is</tt> - The exact size of the attribute
|
334
|
+
# * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
|
335
|
+
# * <tt>in</tt> - A synonym(or alias) for :within
|
336
|
+
# * <tt>allow_nil</tt> - Attribute may be nil; skip validation.
|
337
|
+
# * <tt>allow_blank</tt> - Attribute may be blank; skip validation.
|
338
|
+
#
|
339
|
+
# * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)")
|
340
|
+
# * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
|
341
|
+
# * <tt>wrong_length</tt> - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)")
|
342
|
+
# * <tt>message</tt> - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message
|
343
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
344
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
345
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
346
|
+
# method result or slot should be equal to a true or false value.
|
347
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
348
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
349
|
+
# method result or slot should be equal to a true or false value.
|
350
|
+
RANGE_OPTIONS = %w(is within in minimum maximum).freeze unless defined? RANGE_OPTIONS
|
351
|
+
RANGE_VALIDATIONS = {
|
352
|
+
'is' => [ :==, ERROR_MESSAGES[:wrong_length] ],
|
353
|
+
'minimum' => [ :>=, ERROR_MESSAGES[:too_short] ],
|
354
|
+
'maximum' => [ :<=, ERROR_MESSAGES[:too_long] ]
|
355
|
+
}.freeze unless defined? RANGE_VALIDATIONS
|
356
|
+
|
357
|
+
def validates_length_of(slotname, opts = {})
|
358
|
+
register_validation("length_of", slotname, opts, nil) do |opts|
|
359
|
+
range_options = opts.reject { |opt, val| !RANGE_OPTIONS.include? opt }
|
360
|
+
|
361
|
+
case range_options.size
|
362
|
+
when 0
|
363
|
+
raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
|
364
|
+
when 1
|
365
|
+
# Valid number of options; do nothing.
|
366
|
+
else
|
367
|
+
raise ArgumentError, 'Too many range options specified. Choose only one.'
|
368
|
+
end
|
369
|
+
|
370
|
+
ropt = range_options.keys.first
|
371
|
+
ropt_value = range_options[ropt]
|
372
|
+
|
373
|
+
opthash = {
|
374
|
+
:allow_nil => !!opts['allow_nil'],
|
375
|
+
:allow_blank => !!opts['allow_blank']
|
376
|
+
}
|
377
|
+
|
378
|
+
case ropt
|
379
|
+
when 'within', 'in'
|
380
|
+
raise ArgumentError, ":#{ropt} must be a Range" unless ropt_value.is_a? Range
|
381
|
+
|
382
|
+
opthash[:too_short] = (opts['too_short'] || ERROR_MESSAGES[:too_short]) % ropt_value.begin
|
383
|
+
opthash[:too_long] = (opts['too_long'] || ERROR_MESSAGES[:too_long]) % ropt_value.end
|
384
|
+
opthash[:range] = ropt_value
|
385
|
+
|
386
|
+
when 'is', 'minimum', 'maximum'
|
387
|
+
raise ArgumentError, ":#{ropt} must be a nonnegative Integer" unless ropt_value.is_a?(Integer) and ropt_value >= 0
|
388
|
+
|
389
|
+
# Declare different validations per option.
|
390
|
+
opthash[:message] = (opts['message'] || RANGE_VALIDATIONS[ropt][1]) % ropt_value
|
391
|
+
opthash[:method] = RANGE_VALIDATIONS[ropt][0]
|
392
|
+
opthash[:argument] = ropt_value
|
393
|
+
end
|
394
|
+
|
395
|
+
opthash
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
alias_method :validates_size_of, :validates_length_of
|
400
|
+
|
401
|
+
# Validates whether the associated object or objects are all valid
|
402
|
+
# themselves. Works with any kind of association.
|
403
|
+
#
|
404
|
+
# Book = Meta.new do
|
405
|
+
# has_many :pages
|
406
|
+
#
|
407
|
+
# validates_associated :pages, :library
|
408
|
+
# end
|
409
|
+
#
|
410
|
+
# Warning: If, after the above definition, you then wrote:
|
411
|
+
#
|
412
|
+
# Page = Meta.new do
|
413
|
+
# belongs_to :book
|
414
|
+
#
|
415
|
+
# validates_associated :book
|
416
|
+
# end
|
417
|
+
#
|
418
|
+
# ...this would specify a circular dependency and cause infinite recursion.
|
419
|
+
#
|
420
|
+
# NOTE: This validation will not fail if the association hasn't been
|
421
|
+
# assigned. If you want to ensure that the association is both present and
|
422
|
+
# guaranteed to be valid, you also need to use validates_presence_of (and,
|
423
|
+
# possibly, validates_type_of).
|
424
|
+
#
|
425
|
+
# Configuration options:
|
426
|
+
# * <tt>message</tt> - A custom error message (default is: "is invalid")
|
427
|
+
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
428
|
+
# * <tt>if</tt> - Specifies a method or slot name to call to determine if the validation should
|
429
|
+
# occur (e.g. :if => :allow_validation, or :if => 'signup_step_less_than_three'). The
|
430
|
+
# method result or slot should be equal to a true or false value.
|
431
|
+
# * <tt>unless</tt> - Specifies a method or slot name to call to determine if the validation should
|
432
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => 'signup_step_less_than_three'). The
|
433
|
+
# method result or slot should be equal to a true or false value.
|
434
|
+
def validates_associated(slotname, opts = {})
|
435
|
+
register_validation('associated', slotname, opts, :invalid)
|
436
|
+
end
|
437
|
+
|
438
|
+
# this module gets mixed into Document
|
439
|
+
module InstanceMethods
|
440
|
+
class Errors
|
441
|
+
include Enumerable
|
442
|
+
|
443
|
+
def initialize(base)
|
444
|
+
@base, @errors = base, {}
|
445
|
+
end
|
446
|
+
|
447
|
+
def add(slot, msg)
|
448
|
+
slot = slot.to_s
|
449
|
+
@errors[slot] = [] if @errors[slot].nil?
|
450
|
+
@errors[slot] << msg
|
451
|
+
end
|
452
|
+
|
453
|
+
def invalid?(slot)
|
454
|
+
!@errors[slot.to_s].nil?
|
455
|
+
end
|
456
|
+
|
457
|
+
def on(slot)
|
458
|
+
errors = @errors[slot.to_s]
|
459
|
+
return nil if errors.nil?
|
460
|
+
errors.size == 1 ? errors.first : errors
|
461
|
+
end
|
462
|
+
|
463
|
+
alias :[] :on
|
464
|
+
|
465
|
+
# Returns true if no errors have been added.
|
466
|
+
def empty?
|
467
|
+
@errors.empty?
|
468
|
+
end
|
469
|
+
|
470
|
+
# Removes all errors that have been added.
|
471
|
+
def clear
|
472
|
+
@errors = {}
|
473
|
+
end
|
474
|
+
|
475
|
+
# Returns all the error messages in an array.
|
476
|
+
def messages
|
477
|
+
@errors.values.inject([]) { |error_messages, slot| error_messages + slot }
|
478
|
+
end
|
479
|
+
|
480
|
+
# Returns the total number of errors added. Two errors added to the
|
481
|
+
# same slot will be counted as such.
|
482
|
+
def size
|
483
|
+
@errors.values.inject(0) { |error_count, slot| error_count + slot.size }
|
484
|
+
end
|
485
|
+
|
486
|
+
# Yields each attribute and associated message per error added
|
487
|
+
def each
|
488
|
+
@errors.each_key do |slot|
|
489
|
+
@errors[slot].each do |msg|
|
490
|
+
yield [ msg ]
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
alias_method :count, :size
|
496
|
+
alias_method :length, :size
|
497
|
+
end
|
498
|
+
|
499
|
+
# Runs validations and returns true if no errors were added otherwise false.
|
500
|
+
def valid?
|
501
|
+
errors.clear
|
502
|
+
|
503
|
+
execute_callbacks :on_validation
|
504
|
+
|
505
|
+
errors.empty?
|
506
|
+
end
|
507
|
+
|
508
|
+
# Returns the Errors object that holds all information about attribute
|
509
|
+
# error messages.
|
510
|
+
def errors
|
511
|
+
@errors ||= Errors.new(self)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
private
|
516
|
+
|
517
|
+
def register_validation(validation_name, slotname, opts, message)
|
518
|
+
opts = opts.stringify_keys
|
519
|
+
slotname = slotname.to_s
|
520
|
+
on = (opts['on'] || 'save').to_s.downcase
|
521
|
+
message = opts['message'] || (message.is_a?(Symbol) ? ERROR_MESSAGES[message] : message)
|
522
|
+
|
523
|
+
check_condition(opts['if']) if opts['if']
|
524
|
+
check_condition(opts['unless']) if opts['unless']
|
525
|
+
|
526
|
+
options_hash = {
|
527
|
+
:slotname => slotname,
|
528
|
+
:message => message,
|
529
|
+
:on => on,
|
530
|
+
:if => opts['if'],
|
531
|
+
:unless => opts['unless']
|
532
|
+
}
|
533
|
+
|
534
|
+
options_hash.merge!(yield(opts)) if block_given?
|
535
|
+
|
536
|
+
validation_slot = "validates_#{validation_name}_#{slotname}"
|
537
|
+
|
538
|
+
@meta_initialization_procs << Proc.new do
|
539
|
+
@args.last.reverse_merge!(validation_slot => { :meta => name }.merge(options_hash))
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
NUMERICALITY_ERRORS = {
|
544
|
+
'greater_than' => '#{slotname} must be greater than %d',
|
545
|
+
'greater_than_or_equal_to' => '#{slotname} must be greater than or equal to %d',
|
546
|
+
'equal_to' => '#{slotname} must be equal to %d',
|
547
|
+
'less_than' => '#{slotname} must be less than %d',
|
548
|
+
'less_than_or_equal_to' => '#{slotname} must be less than or equal to %d',
|
549
|
+
'odd' => '#{slotname} must be odd',
|
550
|
+
'even' => '#{slotname} must be even'
|
551
|
+
}.freeze unless defined? NUMERICALITY_ERRORS
|
552
|
+
|
553
|
+
def initialize_validations
|
554
|
+
install_validations_for(:validates_presence_of) do |doc, validation, slotname|
|
555
|
+
doc.has_slot? slotname
|
556
|
+
end
|
557
|
+
|
558
|
+
install_validations_for(:validates_type_of) do |doc, validation, slotname|
|
559
|
+
doc[slotname].is_a? Kernel.const_get(validation[:validation_type])
|
560
|
+
end
|
561
|
+
|
562
|
+
install_validations_for(:validates_inclusion_of) do |doc, validation, slotname|
|
563
|
+
validation[:in].include? doc[slotname]
|
564
|
+
end
|
565
|
+
|
566
|
+
install_validations_for(:validates_exclusion_of) do |doc, validation, slotname|
|
567
|
+
!validation[:in].include?(doc[slotname])
|
568
|
+
end
|
569
|
+
|
570
|
+
install_validations_for(:validates_format_of) do |doc, validation, slotname|
|
571
|
+
!(doc[slotname] !~ validation[:with])
|
572
|
+
end
|
573
|
+
|
574
|
+
install_validations_for(:validates_uniqueness_of) do |doc, validation, slotname|
|
575
|
+
meta = Kernel.const_get(doc.meta.name)
|
576
|
+
|
577
|
+
!(found = meta.find(slotname.to_sym => doc[slotname])) ||
|
578
|
+
(found.size == 0) ||
|
579
|
+
(found.first.uuid == doc.uuid)
|
580
|
+
end
|
581
|
+
|
582
|
+
# using lambda here enables us to use return
|
583
|
+
numericality = lambda do |doc, validation, slotname|
|
584
|
+
value = doc[slotname]
|
585
|
+
|
586
|
+
if validation[:only_integer]
|
587
|
+
return (validation[:message] || "#{slotname} must be integer") unless value.to_s =~ /\A[+-]?\d+\Z/
|
588
|
+
value = value.to_i
|
589
|
+
else
|
590
|
+
value = Kernel.Float(value) rescue false
|
591
|
+
return (validation[:message] || "#{slotname} is not a number") unless value
|
592
|
+
end
|
593
|
+
|
594
|
+
errors = []
|
595
|
+
|
596
|
+
validation[:numeric_checks].each do |option, optvalue|
|
597
|
+
testresult = if %w(odd even).include? option
|
598
|
+
value.to_i.send(NUMERICALITY_CHECKS[option])
|
599
|
+
else
|
600
|
+
value.send(NUMERICALITY_CHECKS[option], optvalue)
|
601
|
+
end
|
602
|
+
|
603
|
+
unless testresult
|
604
|
+
errors << ((validation[:message] || NUMERICALITY_ERRORS[option]) % optvalue)
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
errors.empty? ? true : errors
|
609
|
+
end
|
610
|
+
|
611
|
+
install_validations_for(:validates_numericality_of, &numericality)
|
612
|
+
install_validations_for(:validates_confirmation_of) do |doc, validation, slotname|
|
613
|
+
confirm_slotname = slotname + "_confirmation"
|
614
|
+
!doc.has_slot?(confirm_slotname) || doc[slotname] == doc[confirm_slotname]
|
615
|
+
end
|
616
|
+
|
617
|
+
install_validations_for(:validates_acceptance_of) do |doc, validation, slotname|
|
618
|
+
doc[slotname] == validation[:accept]
|
619
|
+
end
|
620
|
+
|
621
|
+
install_validations_for(:validates_length_of) do |doc, validation, slotname|
|
622
|
+
value = doc[slotname]
|
623
|
+
size = case value
|
624
|
+
when nil then 0
|
625
|
+
when String then value.split(//).size
|
626
|
+
else
|
627
|
+
value.size
|
628
|
+
end
|
629
|
+
|
630
|
+
if range = validation[:range]
|
631
|
+
if value.nil? or size < range.begin
|
632
|
+
validation[:too_short]
|
633
|
+
elsif size > range.end
|
634
|
+
validation[:too_long]
|
635
|
+
else
|
636
|
+
true
|
637
|
+
end
|
638
|
+
else
|
639
|
+
!value.nil? && size.send(validation[:method], validation[:argument])
|
640
|
+
end
|
641
|
+
end
|
642
|
+
|
643
|
+
install_validations_for(:validates_associated) do |doc, validation, slotname|
|
644
|
+
begin
|
645
|
+
result = false
|
646
|
+
Util.catch_circular_reference(doc,:validates_associated_reference_stack) do
|
647
|
+
if doc.has_slot?(slotname)
|
648
|
+
val = doc[slotname]
|
649
|
+
|
650
|
+
if val.respond_to? :inject
|
651
|
+
result = val.inject(true) { |prev, associate| prev && (associate.respond_to?(:valid?) ? associate.valid? : true) }
|
652
|
+
else
|
653
|
+
result = val.respond_to?(:valid?) ? val.valid? : true
|
654
|
+
end
|
655
|
+
else
|
656
|
+
result = true
|
657
|
+
end
|
658
|
+
end
|
659
|
+
rescue Util::CircularReferenceCondition
|
660
|
+
result = true
|
661
|
+
end
|
662
|
+
result
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
def install_validations_for(sym, &block)
|
667
|
+
on_validation(sym) do |doc|
|
668
|
+
grep_slots(doc, sym.to_s + "_") do |slotname_to_validate, meta_slotname|
|
669
|
+
next unless validation = doc.meta[meta_slotname]
|
670
|
+
|
671
|
+
on = validation['on']
|
672
|
+
|
673
|
+
next unless (on == 'create' && doc.new?) || (on == 'update' && !doc.new?) || on == 'save'
|
674
|
+
|
675
|
+
next if validation[:if] && !evaluate_condition(validation[:if], doc)
|
676
|
+
next if validation[:unless] && evaluate_condition(validation[:unless], doc)
|
677
|
+
|
678
|
+
value = doc[slotname_to_validate]
|
679
|
+
|
680
|
+
next if validation[:allow_nil] && value.nil?
|
681
|
+
next if validation[:allow_blank] && value.blank?
|
682
|
+
|
683
|
+
case validation_result = block.call(doc, validation, slotname_to_validate)
|
684
|
+
when true then next
|
685
|
+
when false
|
686
|
+
add_error(doc, validation, slotname_to_validate, validation[:message])
|
687
|
+
when String
|
688
|
+
add_error(doc, validation, slotname_to_validate, validation_result)
|
689
|
+
when Array
|
690
|
+
validation_result.each { |message| add_error(doc, validation, slotname_to_validate, message) }
|
691
|
+
end
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
def add_error(doc, validation, slotname, message)
|
697
|
+
os = OpenStruct.new(validation)
|
698
|
+
os.document = doc
|
699
|
+
os.slotvalue = doc[slotname]
|
700
|
+
|
701
|
+
doc.errors.add(slotname, os.instance_eval("\"#{message}\""))
|
702
|
+
end
|
703
|
+
end
|
704
|
+
end
|