ant-mapper 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/CHANGE +6 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +25 -0
  4. data/ant.gemspec +48 -0
  5. data/data.tch +0 -0
  6. data/examples/account.rb +71 -0
  7. data/examples/data.tch +0 -0
  8. data/examples/light_cloud.yml +18 -0
  9. data/examples/user.rb +84 -0
  10. data/init.rb +4 -0
  11. data/lib/ant.rb +7 -0
  12. data/lib/ant_mapper.rb +10 -0
  13. data/lib/ant_mapper/adapters/light_cloud.rb +59 -0
  14. data/lib/ant_mapper/adapters/tokyo_cabinet.rb +42 -0
  15. data/lib/ant_mapper/adapters/tokyo_tyrant.rb +14 -0
  16. data/lib/ant_mapper/base.rb +367 -0
  17. data/lib/ant_mapper/callbacks.rb +180 -0
  18. data/lib/ant_mapper/observer.rb +180 -0
  19. data/lib/ant_mapper/validations.rb +687 -0
  20. data/lib/ant_support.rb +4 -0
  21. data/lib/ant_support/callbacks.rb +303 -0
  22. data/lib/ant_support/core_ext.rb +4 -0
  23. data/lib/ant_support/core_ext/array.rb +5 -0
  24. data/lib/ant_support/core_ext/array/extract_options.rb +20 -0
  25. data/lib/ant_support/core_ext/blank.rb +58 -0
  26. data/lib/ant_support/core_ext/class.rb +3 -0
  27. data/lib/ant_support/core_ext/class/attribute_accessors.rb +54 -0
  28. data/lib/ant_support/core_ext/class/inheritable_attributes.rb +140 -0
  29. data/lib/ant_support/core_ext/class/removal.rb +50 -0
  30. data/lib/ant_support/core_ext/duplicable.rb +43 -0
  31. data/lib/ant_support/core_ext/enumerable.rb +72 -0
  32. data/lib/ant_support/core_ext/hash.rb +6 -0
  33. data/lib/ant_support/core_ext/hash/keys.rb +52 -0
  34. data/lib/ant_support/core_ext/module.rb +16 -0
  35. data/lib/ant_support/core_ext/module/aliasing.rb +74 -0
  36. data/lib/ant_support/core_ext/module/attr_accessor_with_default.rb +31 -0
  37. data/lib/ant_support/core_ext/module/attribute_accessors.rb +58 -0
  38. data/lib/ant_support/core_ext/object.rb +1 -0
  39. data/lib/ant_support/core_ext/object/extending.rb +80 -0
  40. data/lib/ant_support/core_ext/string.rb +7 -0
  41. data/lib/ant_support/core_ext/string/inflections.rb +51 -0
  42. data/spec/case/callbacks_observers_test.rb +38 -0
  43. data/spec/case/callbacks_test.rb +417 -0
  44. data/spec/case/create_object_test.rb +56 -0
  45. data/spec/case/set_class_name_test.rb +17 -0
  46. data/spec/case/validations_test.rb +1482 -0
  47. data/spec/helper.rb +15 -0
  48. data/spec/light_cloud.yml +18 -0
  49. data/spec/model/account.rb +3 -0
  50. data/spec/model/topic.rb +28 -0
  51. data/spec/model/user.rb +4 -0
  52. metadata +125 -0
@@ -0,0 +1,180 @@
1
+ require 'observer'
2
+
3
+ module AntMapper
4
+ # Callbacks是一个Ant Mapper对象生命周期内的钩子函数,允许你在对象状态改变的之前或之后触发相关规则.
5
+
6
+ # 当一个新对象的 <tt>Base#save</tt> 方法被调用时:
7
+ #
8
+ # * (-) <tt>save</tt>
9
+ # * (-) <tt>valid</tt>
10
+ # * (1) <tt>before_validation</tt>
11
+ # * (2) <tt>before_validation_on_create</tt>
12
+ # * (-) <tt>validate</tt>
13
+ # * (-) <tt>validate_on_create</tt>
14
+ # * (3) <tt>after_validation</tt>
15
+ # * (4) <tt>after_validation_on_create</tt>
16
+ # * (5) <tt>before_save</tt>
17
+ # * (6) <tt>before_create</tt>
18
+ # * (-) <tt>create</tt>
19
+ # * (7) <tt>after_create</tt>
20
+ # * (8) <tt>after_save</tt>
21
+ #
22
+ # 这里总共有8个回调.已经存在的记录调用<tt>Base#save</tt>时,除了<tt>_on_create</tt> 回调被<tt>_on_update</tt>取代外,其它都一样.
23
+ #
24
+ # 示例:
25
+ # class CreditCard < AntMapper::Base
26
+ # # Strip everything but digits, so the user can specify "555 234 34" or
27
+ # # "5552-3434" or both will mean "55523434"
28
+ # def before_validation_on_create
29
+ # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
30
+ # end
31
+ # end
32
+ #
33
+ # class Subscription < AntMapper::Base
34
+ # before_create :record_signup
35
+ #
36
+ # private
37
+ # def record_signup
38
+ # self.signed_up_on = Date.today
39
+ # end
40
+ # end
41
+ #
42
+ #
43
+ # == 继承回调序列
44
+ #
45
+ # 除了重写回调方法外,它也可以通过使用回调宏注册回调.宏能添加行为到回调序列,这样在多级继承时,能保留回调序列的完整。
46
+ # 示例:
47
+ #
48
+ # class Topic < AntMapper::Base
49
+ # before_destroy :destroy_author
50
+ # end
51
+ #
52
+ # class Reply < Topic
53
+ # before_destroy :destroy_readers
54
+ # end
55
+ #
56
+ # 现在, 当 <tt>Topic#destroy</tt> 运行时只调用 +destroy_author+.当<tt>Reply#destroy</tt>运行时,将调用+destroy_author+ 和 # +destroy_readers+ 。
57
+ #
58
+ #
59
+ module Callbacks
60
+ CALLBACKS = %w(
61
+ after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
62
+ after_validation before_validation_on_create after_validation_on_create before_validation_on_update
63
+ after_validation_on_update before_destroy after_destroy
64
+ )
65
+
66
+ def self.included(base) #:nodoc:
67
+ base.extend Observable
68
+
69
+ [:initialize,:create_or_update, :valid?, :create, :update, :destroy].each do |method|
70
+ base.send :alias_method_chain, method, :callbacks
71
+ end
72
+
73
+ base.send :include, AntSupport::Callbacks
74
+ base.define_callbacks *CALLBACKS
75
+ end
76
+
77
+ # 在调用<tt>Base.new</tt>时,对象被初始化之后执行。
78
+ def after_initialize() end
79
+
80
+ def initialize_with_callbacks(*args)
81
+ result = initialize_without_callbacks(*args)
82
+ callback(:after_initialize)
83
+ result
84
+ end
85
+
86
+ # 在执行<tt>Base.save</tt>之前被调用 (不管是创建还是更新).
87
+ def before_save() end
88
+
89
+ # 在执行<tt>Base.save</tt>被调用(不管是创建还是更新).
90
+ #
91
+ # class Contact < AntMapper::Base
92
+ # after_save { logger.info( 'New contact saved!' ) }
93
+ # end
94
+ def after_save() end
95
+ def create_or_update_with_callbacks #:nodoc:
96
+ return false if callback(:before_save) == false
97
+ result = create_or_update_without_callbacks
98
+ callback(:after_save)
99
+ result
100
+ end
101
+ private :create_or_update_with_callbacks
102
+
103
+
104
+ def before_create() end
105
+
106
+ def after_create() end
107
+ def create_with_callbacks #:nodoc:
108
+ return false if callback(:before_create) == false
109
+ result = create_without_callbacks
110
+ callback(:after_create)
111
+ result
112
+ end
113
+ private :create_with_callbacks
114
+
115
+ def before_update() end
116
+
117
+ def after_update() end
118
+
119
+ def update_with_callbacks(*args) #:nodoc:
120
+ return false if callback(:before_update) == false
121
+ result = update_without_callbacks(*args)
122
+ callback(:after_update)
123
+ result
124
+ end
125
+ private :update_with_callbacks
126
+
127
+ def before_validation() end
128
+
129
+ def after_validation() end
130
+
131
+ def before_validation_on_create() end
132
+
133
+ def after_validation_on_create() end
134
+
135
+ def before_validation_on_update() end
136
+
137
+ def after_validation_on_update() end
138
+
139
+ def valid_with_callbacks? #:nodoc:
140
+ return false if callback(:before_validation) == false
141
+ if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
142
+ return false if false == result
143
+
144
+ result = valid_without_callbacks?
145
+
146
+ callback(:after_validation)
147
+ if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
148
+
149
+ return result
150
+ end
151
+
152
+ def before_destroy() end
153
+
154
+ def after_destroy() end
155
+ def destroy_with_callbacks #:nodoc:
156
+ return false if callback(:before_destroy) == false
157
+ result = destroy_without_callbacks
158
+ callback(:after_destroy)
159
+ result
160
+ end
161
+
162
+ private
163
+ def callback(method)
164
+ result = run_callbacks(method) { |result, object| false == result }
165
+
166
+ if result != false #&& respond_to_without_attributes?(method)
167
+ result = send(method)
168
+ end
169
+
170
+ notify(method)
171
+
172
+ return result
173
+ end
174
+
175
+ def notify(method) #:nodoc:
176
+ self.class.changed
177
+ self.class.notify_observers(method, self)
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,180 @@
1
+ require 'singleton'
2
+ require 'set'
3
+
4
+ module AntMapper
5
+ module Observing # :nodoc:
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ # 激活观察器. 示例:
12
+ #
13
+ # # 调用PersonObserver.instance
14
+ # AntMapper::Base.observers = :person_observer
15
+ #
16
+ # # 调用Cacher.instance 和 GarbageCollector.instance
17
+ # AntMapper::Base.observers = :cacher, :garbage_collector
18
+ #
19
+ # # 通上
20
+ # AntMapper::Base.observers = Cacher, GarbageCollector
21
+ #
22
+ # Note: Setting this does not instantiate the observers yet. +instantiate_observers+ is
23
+ # called during startup, and before each development request.
24
+ def observers=(*observers)
25
+ @observers = observers.flatten
26
+ end
27
+
28
+ # 获得当前observers.
29
+ def observers
30
+ @observers ||= []
31
+ end
32
+
33
+ # 实例化全部Ant Mapper观察器.
34
+ def instantiate_observers
35
+ return if @observers.blank?
36
+ @observers.each do |observer|
37
+ if observer.respond_to?(:to_sym) # Symbol or String
38
+ observer.to_s.camelize.constantize.instance
39
+ elsif observer.respond_to?(:instance)
40
+ observer.instance
41
+ else
42
+ raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
43
+ end
44
+ end
45
+ end
46
+
47
+ protected
48
+ # Notify observers when the observed class is subclassed.
49
+ def inherited(subclass)
50
+ super
51
+ changed
52
+ notify_observers :observed_class_inherited, subclass
53
+ end
54
+ end
55
+ end
56
+
57
+ # Observer classes respond to lifecycle callbacks to implement trigger-like
58
+ # behavior outside the original class. This is a great way to reduce the
59
+ # clutter that normally comes when the model class is burdened with
60
+ # functionality that doesn't pertain to the core responsibility of the
61
+ # class. Example:
62
+ #
63
+ # class CommentObserver < AntMapper::Observer
64
+ # def after_save(comment)
65
+ # Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
66
+ # end
67
+ # end
68
+ #
69
+ # This Observer sends an email when a Comment#save is finished.
70
+ #
71
+ # class ContactObserver < AntMapper::Observer
72
+ # def after_create(contact)
73
+ # contact.logger.info('New contact added!')
74
+ # end
75
+ #
76
+ # def after_destroy(contact)
77
+ # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
78
+ # end
79
+ # end
80
+ #
81
+ # This Observer uses logger to log when specific callbacks are triggered.
82
+ #
83
+ # == Observing a class that can't be inferred
84
+ #
85
+ # Observers will by default be mapped to the class with which they share a name. So CommentObserver will
86
+ # be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
87
+ # differently than the class you're interested in observing, you can use the Observer.observe class method which takes
88
+ # either the concrete class (Product) or a symbol for that class (:product):
89
+ #
90
+ # class AuditObserver < AntMapper::Observer
91
+ # observe :account
92
+ #
93
+ # def after_update(account)
94
+ # AuditTrail.new(account, "UPDATED")
95
+ # end
96
+ # end
97
+ #
98
+ # If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
99
+ #
100
+ # class AuditObserver < AntMapper::Observer
101
+ # observe :account, :balance
102
+ #
103
+ # def after_update(record)
104
+ # AuditTrail.new(record, "UPDATED")
105
+ # end
106
+ # end
107
+ #
108
+ # The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
109
+ #
110
+ # == Available callback methods
111
+ #
112
+ # The observer can implement callback methods for each of the methods described in the Callbacks module.
113
+ #
114
+ #
115
+ # == Loading
116
+ #
117
+ # Observers register themselves in the model class they observe, since it is the class that
118
+ # notifies them of events when they occur. As a side-effect, when an observer is loaded its
119
+ # corresponding model class is loaded.
120
+ #
121
+ # If by any chance you are using observed models in the initialization you can still
122
+ # load their observers by calling <tt>ModelObserver.instance</tt> before. Observers are
123
+ # singletons and that call instantiates and registers them.
124
+ #
125
+ class Observer
126
+ include Singleton
127
+
128
+ class << self
129
+ # Attaches the observer to the supplied model classes.
130
+ def observe(*models)
131
+ models.flatten!
132
+ models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
133
+ define_method(:observed_classes) { Set.new(models) }
134
+ end
135
+
136
+ # The class observed by default is inferred from the observer's class name:
137
+ # assert_equal Person, PersonObserver.observed_class
138
+ def observed_class
139
+ if observed_class_name = name[/(.*)Observer/, 1]
140
+ observed_class_name.constantize
141
+ else
142
+ nil
143
+ end
144
+ end
145
+ end
146
+
147
+ # Start observing the declared classes and their subclasses.
148
+ def initialize
149
+ Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
150
+ end
151
+
152
+ # Send observed_method(object) if the method exists.
153
+ def update(observed_method, object) #:nodoc:
154
+ send(observed_method, object) if respond_to?(observed_method)
155
+ end
156
+
157
+ # Special method sent by the observed class when it is inherited.
158
+ # Passes the new subclass.
159
+ def observed_class_inherited(subclass) #:nodoc:
160
+ self.class.observe(observed_classes + [subclass])
161
+ add_observer!(subclass)
162
+ end
163
+
164
+ protected
165
+ def observed_classes
166
+ Set.new([self.class.observed_class].compact.flatten)
167
+ end
168
+
169
+ def observed_subclasses
170
+ observed_classes.sum([]) { |klass| klass.send(:subclasses) }
171
+ end
172
+
173
+ def add_observer!(klass)
174
+ klass.add_observer(self)
175
+ if respond_to?(:after_find) && !klass.method_defined?(:after_find)
176
+ klass.class_eval 'def after_find() end'
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,687 @@
1
+ module AntMapper
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 AntMapper::ObjectInvalid => invalid
7
+ # puts invalid.record.errors
8
+ # end
9
+ class ObjectInvalid < AntMapperError
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 < AntMapper::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 < AntMapper::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 < AntMapper::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, AntSupport::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 < AntMapper::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 < AntMapper::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 < AntMapper::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 < AntMapper::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 < AntMapper::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 < AntMapper::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 => AntMapper::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 < AntMapper::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 => AntMapper::Errors.default_error_messages[:too_long],
431
+ :too_short => AntMapper::Errors.default_error_messages[:too_short],
432
+ :wrong_length => AntMapper::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 < AntMapper::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 => AntMapper::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 < AntMapper::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 => AntMapper::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 < AntMapper::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 => AntMapper::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 < AntMapper::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 => AntMapper::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