activeobject 0.0.3

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.
Files changed (80) hide show
  1. data/CHANGE +10 -0
  2. data/Interface_desc +21 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README +72 -0
  5. data/Rakefile.rb +9 -0
  6. data/active-object.gemspec +50 -0
  7. data/examples/account.rb +69 -0
  8. data/examples/data.tch +0 -0
  9. data/examples/light_cloud.yml +18 -0
  10. data/examples/test.rb +3 -0
  11. data/examples/user.rb +112 -0
  12. data/init.rb +4 -0
  13. data/lib/active-object.rb +23 -0
  14. data/lib/active_object/adapters/light_cloud.rb +40 -0
  15. data/lib/active_object/adapters/tokyo_cabinet.rb +48 -0
  16. data/lib/active_object/adapters/tokyo_tyrant.rb +14 -0
  17. data/lib/active_object/associations.rb +200 -0
  18. data/lib/active_object/base.rb +415 -0
  19. data/lib/active_object/callbacks.rb +180 -0
  20. data/lib/active_object/observer.rb +180 -0
  21. data/lib/active_object/serialization.rb +99 -0
  22. data/lib/active_object/serializers/json_serializer.rb +75 -0
  23. data/lib/active_object/serializers/xml_serializer.rb +325 -0
  24. data/lib/active_object/validations.rb +687 -0
  25. data/lib/active_support/callbacks.rb +303 -0
  26. data/lib/active_support/core_ext/array/access.rb +53 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +183 -0
  28. data/lib/active_support/core_ext/array/extract_options.rb +20 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +106 -0
  30. data/lib/active_support/core_ext/array/random_access.rb +12 -0
  31. data/lib/active_support/core_ext/array.rb +13 -0
  32. data/lib/active_support/core_ext/blank.rb +58 -0
  33. data/lib/active_support/core_ext/class/attribute_accessors.rb +54 -0
  34. data/lib/active_support/core_ext/class/inheritable_attributes.rb +140 -0
  35. data/lib/active_support/core_ext/class/removal.rb +50 -0
  36. data/lib/active_support/core_ext/class.rb +3 -0
  37. data/lib/active_support/core_ext/duplicable.rb +43 -0
  38. data/lib/active_support/core_ext/enumerable.rb +72 -0
  39. data/lib/active_support/core_ext/hash/conversions.rb +259 -0
  40. data/lib/active_support/core_ext/hash/keys.rb +52 -0
  41. data/lib/active_support/core_ext/hash.rb +8 -0
  42. data/lib/active_support/core_ext/module/aliasing.rb +74 -0
  43. data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +31 -0
  44. data/lib/active_support/core_ext/module/attribute_accessors.rb +58 -0
  45. data/lib/active_support/core_ext/module.rb +16 -0
  46. data/lib/active_support/core_ext/object/conversions.rb +14 -0
  47. data/lib/active_support/core_ext/object/extending.rb +80 -0
  48. data/lib/active_support/core_ext/object/instance_variables.rb +74 -0
  49. data/lib/active_support/core_ext/object/metaclass.rb +13 -0
  50. data/lib/active_support/core_ext/object/misc.rb +43 -0
  51. data/lib/active_support/core_ext/object.rb +5 -0
  52. data/lib/active_support/core_ext/string/inflections.rb +167 -0
  53. data/lib/active_support/core_ext/string.rb +7 -0
  54. data/lib/active_support/core_ext.rb +4 -0
  55. data/lib/active_support/inflections.rb +55 -0
  56. data/lib/active_support/inflector.rb +348 -0
  57. data/lib/active_support/vendor/builder-2.1.2/blankslate.rb +113 -0
  58. data/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb +20 -0
  59. data/lib/active_support/vendor/builder-2.1.2/builder/css.rb +250 -0
  60. data/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb +115 -0
  61. data/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb +139 -0
  62. data/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb +63 -0
  63. data/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb +328 -0
  64. data/lib/active_support/vendor/builder-2.1.2/builder.rb +13 -0
  65. data/lib/active_support/vendor/xml-simple-1.0.11/xmlsimple.rb +1021 -0
  66. data/lib/active_support/vendor.rb +14 -0
  67. data/lib/active_support.rb +6 -0
  68. data/spec/case/association_test.rb +97 -0
  69. data/spec/case/base_test.rb +74 -0
  70. data/spec/case/callbacks_observers_test.rb +38 -0
  71. data/spec/case/callbacks_test.rb +424 -0
  72. data/spec/case/serialization_test.rb +87 -0
  73. data/spec/case/validations_test.rb +1482 -0
  74. data/spec/data.tch +0 -0
  75. data/spec/helper.rb +15 -0
  76. data/spec/light_cloud.yml +18 -0
  77. data/spec/model/account.rb +4 -0
  78. data/spec/model/topic.rb +26 -0
  79. data/spec/model/user.rb +8 -0
  80. 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