activeobject 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGE +10 -0
- data/Interface_desc +21 -0
- data/MIT-LICENSE +20 -0
- data/README +72 -0
- data/Rakefile.rb +9 -0
- data/active-object.gemspec +50 -0
- data/examples/account.rb +69 -0
- data/examples/data.tch +0 -0
- data/examples/light_cloud.yml +18 -0
- data/examples/test.rb +3 -0
- data/examples/user.rb +112 -0
- data/init.rb +4 -0
- data/lib/active-object.rb +23 -0
- data/lib/active_object/adapters/light_cloud.rb +40 -0
- data/lib/active_object/adapters/tokyo_cabinet.rb +48 -0
- data/lib/active_object/adapters/tokyo_tyrant.rb +14 -0
- data/lib/active_object/associations.rb +200 -0
- data/lib/active_object/base.rb +415 -0
- data/lib/active_object/callbacks.rb +180 -0
- data/lib/active_object/observer.rb +180 -0
- data/lib/active_object/serialization.rb +99 -0
- data/lib/active_object/serializers/json_serializer.rb +75 -0
- data/lib/active_object/serializers/xml_serializer.rb +325 -0
- data/lib/active_object/validations.rb +687 -0
- data/lib/active_support/callbacks.rb +303 -0
- data/lib/active_support/core_ext/array/access.rb +53 -0
- data/lib/active_support/core_ext/array/conversions.rb +183 -0
- data/lib/active_support/core_ext/array/extract_options.rb +20 -0
- data/lib/active_support/core_ext/array/grouping.rb +106 -0
- data/lib/active_support/core_ext/array/random_access.rb +12 -0
- data/lib/active_support/core_ext/array.rb +13 -0
- data/lib/active_support/core_ext/blank.rb +58 -0
- data/lib/active_support/core_ext/class/attribute_accessors.rb +54 -0
- data/lib/active_support/core_ext/class/inheritable_attributes.rb +140 -0
- data/lib/active_support/core_ext/class/removal.rb +50 -0
- data/lib/active_support/core_ext/class.rb +3 -0
- data/lib/active_support/core_ext/duplicable.rb +43 -0
- data/lib/active_support/core_ext/enumerable.rb +72 -0
- data/lib/active_support/core_ext/hash/conversions.rb +259 -0
- data/lib/active_support/core_ext/hash/keys.rb +52 -0
- data/lib/active_support/core_ext/hash.rb +8 -0
- data/lib/active_support/core_ext/module/aliasing.rb +74 -0
- data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +31 -0
- data/lib/active_support/core_ext/module/attribute_accessors.rb +58 -0
- data/lib/active_support/core_ext/module.rb +16 -0
- data/lib/active_support/core_ext/object/conversions.rb +14 -0
- data/lib/active_support/core_ext/object/extending.rb +80 -0
- data/lib/active_support/core_ext/object/instance_variables.rb +74 -0
- data/lib/active_support/core_ext/object/metaclass.rb +13 -0
- data/lib/active_support/core_ext/object/misc.rb +43 -0
- data/lib/active_support/core_ext/object.rb +5 -0
- data/lib/active_support/core_ext/string/inflections.rb +167 -0
- data/lib/active_support/core_ext/string.rb +7 -0
- data/lib/active_support/core_ext.rb +4 -0
- data/lib/active_support/inflections.rb +55 -0
- data/lib/active_support/inflector.rb +348 -0
- data/lib/active_support/vendor/builder-2.1.2/blankslate.rb +113 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb +20 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/css.rb +250 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb +115 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb +139 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb +63 -0
- data/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb +328 -0
- data/lib/active_support/vendor/builder-2.1.2/builder.rb +13 -0
- data/lib/active_support/vendor/xml-simple-1.0.11/xmlsimple.rb +1021 -0
- data/lib/active_support/vendor.rb +14 -0
- data/lib/active_support.rb +6 -0
- data/spec/case/association_test.rb +97 -0
- data/spec/case/base_test.rb +74 -0
- data/spec/case/callbacks_observers_test.rb +38 -0
- data/spec/case/callbacks_test.rb +424 -0
- data/spec/case/serialization_test.rb +87 -0
- data/spec/case/validations_test.rb +1482 -0
- data/spec/data.tch +0 -0
- data/spec/helper.rb +15 -0
- data/spec/light_cloud.yml +18 -0
- data/spec/model/account.rb +4 -0
- data/spec/model/topic.rb +26 -0
- data/spec/model/user.rb +8 -0
- metadata +173 -0
@@ -0,0 +1,687 @@
|
|
1
|
+
module ActiveObject
|
2
|
+
# Raised by <tt>save!</tt> and <tt>create!</tt> when the object is invalid. Use the
|
3
|
+
# +object+ method to retrieve the object which did not validate.
|
4
|
+
# begin
|
5
|
+
# complex_operation_that_calls_save!_internally
|
6
|
+
# rescue ActiveObject::ObjectInvalid => invalid
|
7
|
+
# puts invalid.record.errors
|
8
|
+
# end
|
9
|
+
class ObjectInvalid < ActiveObjectError
|
10
|
+
attr_reader :object
|
11
|
+
def initialize(object)
|
12
|
+
@object = object
|
13
|
+
super("Validation failed: #{@object.errors.full_messages.join(", ")}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Errors
|
18
|
+
include Enumerable
|
19
|
+
|
20
|
+
def initialize(base) # :nodoc:
|
21
|
+
@base, @errors = base, {}
|
22
|
+
end
|
23
|
+
|
24
|
+
@@default_error_messages = {
|
25
|
+
:inclusion => "is not included in the list",
|
26
|
+
:exclusion => "is reserved",
|
27
|
+
:invalid => "is invalid",
|
28
|
+
:confirmation => "doesn't match confirmation",
|
29
|
+
:accepted => "must be accepted",
|
30
|
+
:empty => "can't be empty",
|
31
|
+
:blank => "can't be blank",
|
32
|
+
:too_long => "is too long (maximum is %d characters)",
|
33
|
+
:too_short => "is too short (minimum is %d characters)",
|
34
|
+
:wrong_length => "is the wrong length (should be %d characters)",
|
35
|
+
:taken => "has already been taken",
|
36
|
+
:not_a_number => "is not a number"
|
37
|
+
}
|
38
|
+
|
39
|
+
# Holds a hash with all the default error messages, such that they can be replaced by your own copy or localizations.
|
40
|
+
cattr_accessor :default_error_messages
|
41
|
+
|
42
|
+
# Adds an error to the base object instead of any particular attribute. This is used
|
43
|
+
# to report errors that don't tie to any specific attribute, but rather to the object
|
44
|
+
# as a whole. These error messages don't get prepended with any field name when iterating
|
45
|
+
# with +each_full+, so they should be complete sentences.
|
46
|
+
def add_to_base(msg)
|
47
|
+
add(:base, msg)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Adds an error message (+msg+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
|
51
|
+
# for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
|
52
|
+
# error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
|
53
|
+
# If no +msg+ is supplied, "invalid" is assumed.
|
54
|
+
def add(attribute, msg = @@default_error_messages[:invalid])
|
55
|
+
@errors[attribute.to_s] = [] if @errors[attribute.to_s].nil?
|
56
|
+
@errors[attribute.to_s] << msg
|
57
|
+
end
|
58
|
+
|
59
|
+
# Will add an error message to each of the attributes in +attributes+ that is empty.
|
60
|
+
def add_on_empty(attributes, msg = @@default_error_messages[:empty])
|
61
|
+
for attr in [attributes].flatten
|
62
|
+
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
63
|
+
is_empty = value.respond_to?("empty?") ? value.empty? : false
|
64
|
+
add(attr, msg) unless !value.nil? && !is_empty
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
|
69
|
+
def add_on_blank(attributes, msg = @@default_error_messages[:blank])
|
70
|
+
for attr in [attributes].flatten
|
71
|
+
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
72
|
+
add(attr, msg) if value.blank?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Will add an error message to each of the attributes in +attributes+ that has a length outside of the passed boundary +range+.
|
77
|
+
# If the length is above the boundary, the too_long_msg message will be used. If below, the too_short_msg.
|
78
|
+
def add_on_boundary_breaking(attributes, range, too_long_msg = @@default_error_messages[:too_long], too_short_msg = @@default_error_messages[:too_short])
|
79
|
+
for attr in [attributes].flatten
|
80
|
+
value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
|
81
|
+
add(attr, too_short_msg % range.begin) if value && value.length < range.begin
|
82
|
+
add(attr, too_long_msg % range.end) if value && value.length > range.end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
alias :add_on_boundry_breaking :add_on_boundary_breaking
|
87
|
+
|
88
|
+
def invalid?(attribute)
|
89
|
+
!@errors[attribute.to_s].nil?
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def on(attribute)
|
94
|
+
errors = @errors[attribute.to_s]
|
95
|
+
return nil if errors.nil?
|
96
|
+
errors.size == 1 ? errors.first : errors
|
97
|
+
end
|
98
|
+
|
99
|
+
alias :[] :on
|
100
|
+
|
101
|
+
# Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
|
102
|
+
def on_base
|
103
|
+
on(:base)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Yields each attribute and associated message per error added.
|
107
|
+
#
|
108
|
+
# class Company < ActiveObject::Base
|
109
|
+
# validates_presence_of :name, :address, :email
|
110
|
+
# validates_length_of :name, :in => 5..30
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# company = Company.create(:address => '123 First St.')
|
114
|
+
# company.errors.each{|attr,msg| puts "#{attr} - #{msg}" }
|
115
|
+
# # => name - is too short (minimum is 5 characters)
|
116
|
+
# # name - can't be blank
|
117
|
+
# # address - can't be blank
|
118
|
+
def each
|
119
|
+
@errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
|
123
|
+
# through iteration as "First name can't be empty".
|
124
|
+
#
|
125
|
+
# class Company < ActiveObject::Base
|
126
|
+
# validates_presence_of :name, :address, :email
|
127
|
+
# validates_length_of :name, :in => 5..30
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# company = Company.create(:address => '123 First St.')
|
131
|
+
# company.errors.each_full{|msg| puts msg }
|
132
|
+
# # => Name is too short (minimum is 5 characters)
|
133
|
+
# # Name can't be blank
|
134
|
+
# # Address can't be blank
|
135
|
+
def each_full
|
136
|
+
full_messages.each { |msg| yield msg }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Returns all the full error messages in an array.
|
140
|
+
#
|
141
|
+
# class Company < ActiveObject::Base
|
142
|
+
# validates_presence_of :name, :address, :email
|
143
|
+
# validates_length_of :name, :in => 5..30
|
144
|
+
# end
|
145
|
+
#
|
146
|
+
# company = Company.create(:address => '123 First St.')
|
147
|
+
# company.errors.full_messages # =>
|
148
|
+
# ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
|
149
|
+
def full_messages(options = {})
|
150
|
+
full_messages = []
|
151
|
+
|
152
|
+
@errors.each_key do |attr|
|
153
|
+
@errors[attr].each do |message|
|
154
|
+
next unless message
|
155
|
+
|
156
|
+
if attr == "base"
|
157
|
+
full_messages << message
|
158
|
+
else
|
159
|
+
full_messages << attr.to_s + ' ' + message.to_s
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
full_messages
|
164
|
+
end
|
165
|
+
|
166
|
+
# Returns true if no errors have been added.
|
167
|
+
def empty?
|
168
|
+
@errors.empty?
|
169
|
+
end
|
170
|
+
|
171
|
+
# Removes all errors that have been added.
|
172
|
+
def clear
|
173
|
+
@errors = {}
|
174
|
+
end
|
175
|
+
|
176
|
+
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
|
177
|
+
def size
|
178
|
+
@errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
|
179
|
+
end
|
180
|
+
|
181
|
+
alias_method :count, :size
|
182
|
+
alias_method :length, :size
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
module Validations
|
187
|
+
VALIDATIONS = %w( validate validate_on_create validate_on_update )
|
188
|
+
|
189
|
+
def self.included(base) # :nodoc:
|
190
|
+
base.extend ClassMethods
|
191
|
+
base.class_eval do
|
192
|
+
alias_method_chain :save, :validation
|
193
|
+
alias_method_chain :save!, :validation
|
194
|
+
end
|
195
|
+
|
196
|
+
base.send :include, ActiveSupport::Callbacks
|
197
|
+
base.define_callbacks *VALIDATIONS
|
198
|
+
end
|
199
|
+
|
200
|
+
# Ant Mapper classes can implement validations in several ways. The highest level, easiest to read,
|
201
|
+
# and recommended approach is to use the declarative <tt>validates_..._of</tt> class methods (and
|
202
|
+
# +validates_associated+) documented below. These are sufficient for most model validations.
|
203
|
+
#
|
204
|
+
# Slightly lower level is +validates_each+. It provides some of the same options as the purely declarative
|
205
|
+
# validation methods, but like all the lower-level approaches it requires manually adding to the errors collection
|
206
|
+
# when the record is invalid.
|
207
|
+
#
|
208
|
+
# At a yet lower level, a model can use the class methods +validate+, +validate_on_create+ and +validate_on_update+
|
209
|
+
# to add validation methods or blocks. These are AntSupport::Callbacks and follow the same rules of inheritance
|
210
|
+
# and chaining.
|
211
|
+
#
|
212
|
+
# The lowest level style is to define the instance methods +validate+, +validate_on_create+ and +validate_on_update+
|
213
|
+
# as documented in AntObject::Validations.
|
214
|
+
#
|
215
|
+
# == +validate+, +validate_on_create+ and +validate_on_update+ Class Methods
|
216
|
+
#
|
217
|
+
# Calls to these methods add a validation method or block to the class. Again, this approach is recommended
|
218
|
+
# only when the higher-level methods documented below (<tt>validates_..._of</tt> and +validates_associated+) are
|
219
|
+
# insufficient to handle the required validation.
|
220
|
+
#
|
221
|
+
# This can be done with a symbol pointing to a method:
|
222
|
+
#
|
223
|
+
# class Comment < ActiveObject::Base
|
224
|
+
# validate :must_be_friends
|
225
|
+
#
|
226
|
+
# def must_be_friends
|
227
|
+
# errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
|
228
|
+
# end
|
229
|
+
# end
|
230
|
+
#
|
231
|
+
# Or with a block which is passed the current record to be validated:
|
232
|
+
#
|
233
|
+
# class Comment < ActiveObject::Base
|
234
|
+
# validate do |comment|
|
235
|
+
# comment.must_be_friends
|
236
|
+
# end
|
237
|
+
#
|
238
|
+
# def must_be_friends
|
239
|
+
# errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee)
|
240
|
+
# end
|
241
|
+
# end
|
242
|
+
#
|
243
|
+
# This usage applies to +validate_on_create+ and +validate_on_update+ as well.
|
244
|
+
module ClassMethods
|
245
|
+
DEFAULT_VALIDATION_OPTIONS = {
|
246
|
+
:on => :save,
|
247
|
+
:allow_nil => false,
|
248
|
+
:allow_blank => false,
|
249
|
+
:message => nil
|
250
|
+
}.freeze
|
251
|
+
|
252
|
+
ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
|
253
|
+
ALL_NUMERICALITY_CHECKS = { :greater_than => '>', :greater_than_or_equal_to => '>=',
|
254
|
+
:equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=',
|
255
|
+
:odd => 'odd?', :even => 'even?' }.freeze
|
256
|
+
|
257
|
+
# Validates each attribute against a block.
|
258
|
+
#
|
259
|
+
# class Person < ActiveObject::Base
|
260
|
+
# validates_each :first_name, :last_name do |record, attr, value|
|
261
|
+
# record.errors.add attr, 'starts with z.' if value[0] == ?z
|
262
|
+
# end
|
263
|
+
# end
|
264
|
+
#
|
265
|
+
# Options:
|
266
|
+
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
267
|
+
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
|
268
|
+
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
|
269
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
270
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
271
|
+
# method, proc or string should return or evaluate to a true or false value.
|
272
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
273
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
274
|
+
# method, proc or string should return or evaluate to a true or false value.
|
275
|
+
def validates_each(*attrs)
|
276
|
+
options = attrs.extract_options!.symbolize_keys
|
277
|
+
attrs = attrs.flatten
|
278
|
+
|
279
|
+
# Declare the validation.
|
280
|
+
send(validation_method(options[:on] || :save), options) do |record|
|
281
|
+
attrs.each do |attr|
|
282
|
+
value = record.send(attr)
|
283
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
284
|
+
yield record, attr, value
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
|
290
|
+
#
|
291
|
+
# Model:
|
292
|
+
# class Person < ActiveObject::Base
|
293
|
+
# validates_confirmation_of :user_name, :password
|
294
|
+
# validates_confirmation_of :email_address, :message => "should match confirmation"
|
295
|
+
# end
|
296
|
+
#
|
297
|
+
# View:
|
298
|
+
# <%= password_field "person", "password" %>
|
299
|
+
# <%= password_field "person", "password_confirmation" %>
|
300
|
+
#
|
301
|
+
# The added +password_confirmation+ attribute is virtual; it exists only as an in-memory attribute for validating the password.
|
302
|
+
# To achieve this, the validation adds accessors to the model for the confirmation attribute. NOTE: This check is performed
|
303
|
+
# only if +password_confirmation+ is not +nil+, and by default only on save. To require confirmation, make sure to add a presence
|
304
|
+
# check for the confirmation attribute:
|
305
|
+
#
|
306
|
+
# validates_presence_of :password_confirmation, :if => :password_changed?
|
307
|
+
#
|
308
|
+
# Configuration options:
|
309
|
+
# * <tt>:message</tt> - A custom error message (default is: "doesn't match confirmation").
|
310
|
+
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
311
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
312
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
313
|
+
# method, proc or string should return or evaluate to a true or false value.
|
314
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
315
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
316
|
+
# method, proc or string should return or evaluate to a true or false value.
|
317
|
+
def validates_confirmation_of(*attr_names)
|
318
|
+
configuration = { :on => :save }
|
319
|
+
configuration.update(attr_names.extract_options!)
|
320
|
+
|
321
|
+
attr_accessor(*(attr_names.map { |n| "#{n}_confirmation" }))
|
322
|
+
|
323
|
+
validates_each(attr_names, configuration) do |record, attr_name, value|
|
324
|
+
unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
|
325
|
+
record.errors.add(attr_name, :default => configuration[:message])
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
|
331
|
+
#
|
332
|
+
# class Person < ActiveObject::Base
|
333
|
+
# validates_acceptance_of :terms_of_service
|
334
|
+
# validates_acceptance_of :eula, :message => "must be abided"
|
335
|
+
# end
|
336
|
+
#
|
337
|
+
# If the database column does not exist, the +terms_of_service+ attribute is entirely virtual. This check is
|
338
|
+
# performed only if +terms_of_service+ is not +nil+ and by default on save.
|
339
|
+
#
|
340
|
+
# Configuration options:
|
341
|
+
# * <tt>:message</tt> - A custom error message (default is: "must be accepted").
|
342
|
+
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
343
|
+
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is true).
|
344
|
+
# * <tt>:accept</tt> - Specifies value that is considered accepted. The default value is a string "1", which
|
345
|
+
# makes it easy to relate to an HTML checkbox. This should be set to +true+ if you are validating a database
|
346
|
+
# column, since the attribute is typecast from "1" to +true+ before validation.
|
347
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
348
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
349
|
+
# method, proc or string should return or evaluate to a true or false value.
|
350
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
351
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
352
|
+
# method, proc or string should return or evaluate to a true or false value.
|
353
|
+
def validates_acceptance_of(*attr_names)
|
354
|
+
configuration = { :on => :save, :allow_nil => true, :accept => "1" }
|
355
|
+
configuration.update(attr_names.extract_options!)
|
356
|
+
|
357
|
+
names = attr_names.reject { |name| self.class.attributes.include?(name.to_s) }
|
358
|
+
attr_accessor(*names)
|
359
|
+
|
360
|
+
validates_each(attr_names,configuration) do |record, attr_name, value|
|
361
|
+
unless value == configuration[:accept]
|
362
|
+
record.errors.add(attr_name, :default => configuration[:message])
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
|
368
|
+
#
|
369
|
+
# class Person < ActiveObject::Base
|
370
|
+
# validates_presence_of :first_name
|
371
|
+
# end
|
372
|
+
#
|
373
|
+
# The first_name attribute must be in the object and it cannot be blank.
|
374
|
+
#
|
375
|
+
# If you want to validate the presence of a boolean field (where the real values are true and false),
|
376
|
+
# you will want to use validates_inclusion_of :field_name, :in => [true, false]
|
377
|
+
# This is due to the way Object#blank? handles boolean values. false.blank? # => true
|
378
|
+
#
|
379
|
+
# Configuration options:
|
380
|
+
# * <tt>message</tt> - A custom error message (default is: "can't be blank").
|
381
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
382
|
+
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
383
|
+
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
384
|
+
# method, proc or string should return or evaluate to a true or false value.
|
385
|
+
# * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
386
|
+
# not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
|
387
|
+
# method, proc or string should return or evaluate to a true or false value.
|
388
|
+
#
|
389
|
+
def validates_presence_of(*attr_names)
|
390
|
+
configuration = { :message => ActiveObject::Errors.default_error_messages[:blank], :on => :save }
|
391
|
+
configuration.update(attr_names.extract_options!)
|
392
|
+
|
393
|
+
# can't use validates_each here, because it cannot cope with nonexistent attributes,
|
394
|
+
# while errors.add_on_empty can
|
395
|
+
send(validation_method(configuration[:on]), configuration) do |record|
|
396
|
+
record.errors.add_on_blank(attr_names, configuration[:message])
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
# Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
|
401
|
+
#
|
402
|
+
# class Person < ActiveObject::Base
|
403
|
+
# validates_length_of :first_name, :maximum=>30
|
404
|
+
# validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind"
|
405
|
+
# validates_length_of :fax, :in => 7..32, :allow_nil => true
|
406
|
+
# validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
|
407
|
+
# validates_length_of :fav_bra_size, :minimum=>1, :too_short=>"please enter at least %d character"
|
408
|
+
# validates_length_of :smurf_leader, :is=>4, :message=>"papa is spelled with %d characters... don't play me."
|
409
|
+
# end
|
410
|
+
#
|
411
|
+
# Configuration options:
|
412
|
+
# * <tt>minimum</tt> - The minimum size of the attribute
|
413
|
+
# * <tt>maximum</tt> - The maximum size of the attribute
|
414
|
+
# * <tt>is</tt> - The exact size of the attribute
|
415
|
+
# * <tt>within</tt> - A range specifying the minimum and maximum size of the attribute
|
416
|
+
# * <tt>in</tt> - A synonym(or alias) for :within
|
417
|
+
# * <tt>allow_nil</tt> - Attribute may be nil; skip validation.
|
418
|
+
#
|
419
|
+
# * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)")
|
420
|
+
# * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
|
421
|
+
# * <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)")
|
422
|
+
# * <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
|
423
|
+
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
424
|
+
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
425
|
+
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
426
|
+
# method, proc or string should return or evaluate to a true or false value.
|
427
|
+
def validates_length_of(*attrs)
|
428
|
+
# Merge given options with defaults.
|
429
|
+
options = {
|
430
|
+
:too_long => ActiveObject::Errors.default_error_messages[:too_long],
|
431
|
+
:too_short => ActiveObject::Errors.default_error_messages[:too_short],
|
432
|
+
:wrong_length => ActiveObject::Errors.default_error_messages[:wrong_length]
|
433
|
+
}.merge(DEFAULT_VALIDATION_OPTIONS)
|
434
|
+
options.update(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash)
|
435
|
+
|
436
|
+
# Ensure that one and only one range option is specified.
|
437
|
+
range_options = ALL_RANGE_OPTIONS & options.keys
|
438
|
+
case range_options.size
|
439
|
+
when 0
|
440
|
+
raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
|
441
|
+
when 1
|
442
|
+
# Valid number of options; do nothing.
|
443
|
+
else
|
444
|
+
raise ArgumentError, 'Too many range options specified. Choose only one.'
|
445
|
+
end
|
446
|
+
|
447
|
+
# Get range option and value.
|
448
|
+
option = range_options.first
|
449
|
+
option_value = options[range_options.first]
|
450
|
+
|
451
|
+
case option
|
452
|
+
when :within, :in
|
453
|
+
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
|
454
|
+
|
455
|
+
too_short = options[:too_short] % option_value.begin
|
456
|
+
too_long = options[:too_long] % option_value.end
|
457
|
+
|
458
|
+
validates_each(attrs, options) do |record, attr, value|
|
459
|
+
if value.nil? or value.split(//).size < option_value.begin
|
460
|
+
record.errors.add(attr, too_short)
|
461
|
+
elsif value.split(//).size > option_value.end
|
462
|
+
record.errors.add(attr, too_long)
|
463
|
+
end
|
464
|
+
end
|
465
|
+
when :is, :minimum, :maximum
|
466
|
+
raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
|
467
|
+
|
468
|
+
# Declare different validations per option.
|
469
|
+
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
|
470
|
+
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
|
471
|
+
|
472
|
+
message = (options[:message] || options[message_options[option]]) % option_value
|
473
|
+
|
474
|
+
validates_each(attrs, options) do |record, attr, value|
|
475
|
+
if value.kind_of?(String)
|
476
|
+
record.errors.add(attr, message) unless !value.nil? and value.split(//).size.method(validity_checks[option])[option_value]
|
477
|
+
else
|
478
|
+
record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
alias_method :validates_size_of, :validates_length_of
|
485
|
+
|
486
|
+
|
487
|
+
# Validates whether the value of the specified attribute is of the correct form by matching it against the regular expression
|
488
|
+
# provided.
|
489
|
+
#
|
490
|
+
# class Person < ActiveObject::Base
|
491
|
+
# validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
|
492
|
+
# end
|
493
|
+
#
|
494
|
+
# Note: use \A and \Z to match the start and end of the string, ^ and $ match the start/end of a line.
|
495
|
+
#
|
496
|
+
# A regular expression must be provided or else an exception will be raised.
|
497
|
+
#
|
498
|
+
# Configuration options:
|
499
|
+
# * <tt>message</tt> - A custom error message (default is: "is invalid")
|
500
|
+
# * <tt>with</tt> - The regular expression used to validate the format with (note: must be supplied!)
|
501
|
+
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
502
|
+
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
503
|
+
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
504
|
+
# method, proc or string should return or evaluate to a true or false value.
|
505
|
+
def validates_format_of(*attr_names)
|
506
|
+
configuration = { :message => ActiveObject::Errors.default_error_messages[:invalid], :on => :save, :with => nil }
|
507
|
+
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
508
|
+
|
509
|
+
raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp)
|
510
|
+
|
511
|
+
validates_each(attr_names, configuration) do |record, attr_name, value|
|
512
|
+
record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with]
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
# Validates whether the value of the specified attribute is available in a particular enumerable object.
|
517
|
+
#
|
518
|
+
# class Person < ActiveObject::Base
|
519
|
+
# validates_inclusion_of :gender, :in=>%w( m f ), :message=>"woah! what are you then!??!!"
|
520
|
+
# validates_inclusion_of :age, :in=>0..99
|
521
|
+
# end
|
522
|
+
#
|
523
|
+
# Configuration options:
|
524
|
+
# * <tt>in</tt> - An enumerable object of available items
|
525
|
+
# * <tt>message</tt> - Specifies a customer error message (default is: "is not included in the list")
|
526
|
+
# * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
|
527
|
+
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
528
|
+
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
529
|
+
# method, proc or string should return or evaluate to a true or false value.
|
530
|
+
def validates_inclusion_of(*attr_names)
|
531
|
+
configuration = { :message => ActiveObject::Errors.default_error_messages[:inclusion], :on => :save }
|
532
|
+
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
533
|
+
|
534
|
+
enum = configuration[:in] || configuration[:within]
|
535
|
+
|
536
|
+
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
|
537
|
+
|
538
|
+
validates_each(attr_names, configuration) do |record, attr_name, value|
|
539
|
+
record.errors.add(attr_name, configuration[:message]) unless enum.include?(value)
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
# Validates that the value of the specified attribute is not in a particular enumerable object.
|
544
|
+
#
|
545
|
+
# class Person < ActiveObject::Base
|
546
|
+
# validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
|
547
|
+
# validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
|
548
|
+
# end
|
549
|
+
#
|
550
|
+
# Configuration options:
|
551
|
+
# * <tt>in</tt> - An enumerable object of items that the value shouldn't be part of
|
552
|
+
# * <tt>message</tt> - Specifies a customer error message (default is: "is reserved")
|
553
|
+
# * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false)
|
554
|
+
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
555
|
+
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
556
|
+
# method, proc or string should return or evaluate to a true or false value.
|
557
|
+
def validates_exclusion_of(*attr_names)
|
558
|
+
configuration = { :message => ActiveObject::Errors.default_error_messages[:exclusion], :on => :save }
|
559
|
+
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
560
|
+
|
561
|
+
enum = configuration[:in] || configuration[:within]
|
562
|
+
|
563
|
+
raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?")
|
564
|
+
|
565
|
+
validates_each(attr_names, configuration) do |record, attr_name, value|
|
566
|
+
record.errors.add(attr_name, configuration[:message]) if enum.include?(value)
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
|
571
|
+
# Validates whether the value of the specified attribute is numeric by trying to convert it to
|
572
|
+
# a float with Kernel.Float (if <tt>integer</tt> is false) or applying it to the regular expression
|
573
|
+
# <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>integer</tt> is set to true).
|
574
|
+
#
|
575
|
+
# class Person < ActiveObject::Base
|
576
|
+
# validates_numericality_of :value, :on => :create
|
577
|
+
# end
|
578
|
+
#
|
579
|
+
# Configuration options:
|
580
|
+
# * <tt>message</tt> - A custom error message (default is: "is not a number")
|
581
|
+
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
582
|
+
# * <tt>only_integer</tt> Specifies whether the value has to be an integer, e.g. an integral value (default is false)
|
583
|
+
# * <tt>allow_nil</tt> Skip validation if attribute is nil (default is false). Notice that for fixnum and float columns empty strings are converted to nil
|
584
|
+
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
585
|
+
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
586
|
+
# method, proc or string should return or evaluate to a true or false value.
|
587
|
+
def validates_numericality_of(*attr_names)
|
588
|
+
configuration = { :message => ActiveObject::Errors.default_error_messages[:not_a_number], :on => :save,
|
589
|
+
:only_integer => false, :allow_nil => false }
|
590
|
+
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
591
|
+
|
592
|
+
if configuration[:only_integer]
|
593
|
+
validates_each(attr_names,configuration) do |record, attr_name,value|
|
594
|
+
record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_before_type_cast").to_s =~ /\A[+-]?\d+\Z/
|
595
|
+
end
|
596
|
+
else
|
597
|
+
validates_each(attr_names,configuration) do |record, attr_name,value|
|
598
|
+
next if configuration[:allow_nil] and record.send("#{attr_name}_before_type_cast").nil?
|
599
|
+
begin
|
600
|
+
Kernel.Float(record.send("#{attr_name}_before_type_cast").to_s)
|
601
|
+
rescue ArgumentError, TypeError
|
602
|
+
record.errors.add(attr_name, configuration[:message])
|
603
|
+
end
|
604
|
+
end
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
# Creates an object just like Base.create but calls save! instead of save
|
609
|
+
# so an exception is raised if the record is invalid.
|
610
|
+
def create!(attributes = nil, &block)
|
611
|
+
if attributes.is_a?(Array)
|
612
|
+
attributes.collect { |attr| create!(attr, &block) }
|
613
|
+
else
|
614
|
+
object = new(attributes)
|
615
|
+
yield(object) if block_given?
|
616
|
+
object.save!
|
617
|
+
object
|
618
|
+
end
|
619
|
+
end
|
620
|
+
|
621
|
+
private
|
622
|
+
def validation_method(on)
|
623
|
+
case on
|
624
|
+
when :save then :validate
|
625
|
+
when :create then :validate_on_create
|
626
|
+
when :update then :validate_on_update
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
# The validation process on save can be skipped by passing false. The regular Base#save method is
|
632
|
+
# replaced with this when the validations module is mixed in, which it is by default.
|
633
|
+
def save_with_validation(perform_validation = true)
|
634
|
+
if perform_validation && valid? || !perform_validation
|
635
|
+
save_without_validation
|
636
|
+
else
|
637
|
+
false
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
# Attempts to save the record just like Base#save but will raise a ObjectInvalid exception instead of returning false
|
642
|
+
# if the record is not valid.
|
643
|
+
def save_with_validation!
|
644
|
+
if valid?
|
645
|
+
save_without_validation!
|
646
|
+
else
|
647
|
+
raise ObjectInvalid.new(self)
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
# Runs +validate+ and +validate_on_create+ or +validate_on_update+ and returns true if no errors were added otherwise false.
|
652
|
+
def valid?
|
653
|
+
errors.clear
|
654
|
+
|
655
|
+
run_callbacks(:validate)
|
656
|
+
validate
|
657
|
+
|
658
|
+
if new_record?
|
659
|
+
run_callbacks(:validate_on_create)
|
660
|
+
validate_on_create
|
661
|
+
else
|
662
|
+
run_callbacks(:validate_on_update)
|
663
|
+
validate_on_update
|
664
|
+
end
|
665
|
+
|
666
|
+
errors.empty?
|
667
|
+
end
|
668
|
+
|
669
|
+
# Returns the Errors object that holds all information about attribute error messages.
|
670
|
+
def errors
|
671
|
+
@errors ||= Errors.new(self)
|
672
|
+
end
|
673
|
+
|
674
|
+
protected
|
675
|
+
# Overwrite this method for validation checks on all saves and use <tt>Errors.add(field, msg)</tt> for invalid attributes.
|
676
|
+
def validate #:doc:
|
677
|
+
end
|
678
|
+
|
679
|
+
# Overwrite this method for validation checks used only on creation.
|
680
|
+
def validate_on_create #:doc:
|
681
|
+
end
|
682
|
+
|
683
|
+
# Overwrite this method for validation checks used only on updates.
|
684
|
+
def validate_on_update # :doc:
|
685
|
+
end
|
686
|
+
end
|
687
|
+
end
|