· 8 years ago · Mar 02, 2018, 04:16 AM
1module Validations
2 VALIDATIONS = %w( validate validate_on_create validate_on_update )
3
4 def self.included(base) # :nodoc:
5 base.extend ClassMethods
6 base.class_eval do
7 alias_method_chain :save, :validation
8 alias_method_chain :save!, :validation
9 alias_method_chain :update_attribute, :validation_skipping
10 end
11 end
12
13 # All of the following validations are defined in the class scope of the model that you're interested in validating.
14 # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use
15 # these over the low-level calls to validate and validate_on_create when possible.
16 module ClassMethods
17 DEFAULT_VALIDATION_OPTIONS = {
18 :on => :save,
19 :allow_nil => false,
20 :message => nil
21 }.freeze
22
23 ALL_RANGE_OPTIONS = [ :is, :within, :in, :minimum, :maximum ].freeze
24
25 def validate(*methods, &block)
26 methods << block if block_given?
27 write_inheritable_set(:validate, methods)
28 end
29
30 def validate_on_create(*methods, &block)
31 methods << block if block_given?
32 write_inheritable_set(:validate_on_create, methods)
33 end
34
35 def validate_on_update(*methods, &block)
36 methods << block if block_given?
37 write_inheritable_set(:validate_on_update, methods)
38 end
39
40 def condition_block?(condition)
41 condition.respond_to?("call") && (condition.arity == 1 || condition.arity == -1)
42 end
43
44 # Determine from the given condition (whether a block, procedure, method or string)
45 # whether or not to validate the record. See #validates_each.
46 def evaluate_condition(condition, record)
47 case condition
48 when Symbol: record.send(condition)
49 when String: eval(condition, binding)
50 else
51 if condition_block?(condition)
52 condition.call(record)
53 else
54 raise(
55 ActiveRecordError,
56 "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
57 "class implementing a static validation method"
58 )
59 end
60 end
61 end
62
63 # Validates each attribute against a block.
64 #
65 # class Person < ActiveRecord::Base
66 # validates_each :first_name, :last_name do |record, attr, value|
67 # record.errors.add attr, 'starts with z.' if value[0] == ?z
68 # end
69 # end
70 #
71 # Options:
72 # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
73 # * <tt>allow_nil</tt> - Skip validation if attribute is nil.
74 # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
75 # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
76 # method, proc or string should return or evaluate to a true or false value.
77 def validates_each(*attrs)
78 options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {}
79 attrs = attrs.flatten
80
81 # Declare the validation.
82 send(validation_method(options[:on] || :save)) do |record|
83 # Don't validate when there is an :if condition and that condition is false
84 unless options[:if] && !evaluate_condition(options[:if], record)
85 attrs.each do |attr|
86 value = record.send(attr)
87 next if value.nil? && options[:allow_nil]
88 yield record, attr, value
89 end
90 end
91 end
92 end
93
94 # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
95 #
96 # Model:
97 # class Person < ActiveRecord::Base
98 # validates_confirmation_of :user_name, :password
99 # validates_confirmation_of :email_address, :message => "should match confirmation"
100 # end
101 #
102 # View:
103 # <%= password_field "person", "password" %>
104 # <%= password_field "person", "password_confirmation" %>
105 #
106 # The person has to already have a password attribute (a column in the people table), but the password_confirmation is virtual.
107 # It exists only as an in-memory variable for validating the password. This check is performed only if password_confirmation
108 # is not nil and by default on save.
109 #
110 # Configuration options:
111 # * <tt>message</tt> - A custom error message (default is: "doesn't match confirmation")
112 # * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
113 # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
114 # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
115 # method, proc or string should return or evaluate to a true or false value.
116 def validates_confirmation_of(*attr_names)
117 configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save }
118 configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
119
120 attr_accessor *(attr_names.map { |n| "#{n}_confirmation" })
121
122 validates_each(attr_names, configuration) do |record, attr_name, value|
123 record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation")
124 end
125 end
126
127# ...
128
129
130 # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
131 # if the record is not valid.
132 def save_with_validation!
133 if valid?
134 save_without_validation!
135 else
136 raise RecordInvalid.new(self)
137 end
138 end
139
140 # Updates a single attribute and saves the record without going through the normal validation procedure.
141 # This is especially useful for boolean flags on existing records. The regular +update_attribute+ method
142 # in Base is replaced with this when the validations module is mixed in, which it is by default.
143 def update_attribute_with_validation_skipping(name, value)
144 send(name.to_s + '=', value)
145 save(false)
146 end
147
148 # Runs validate and validate_on_create or validate_on_update and returns true if no errors were added otherwise false.
149 def valid?
150 errors.clear
151
152 run_validations(:validate)
153 validate
154
155 if new_record?
156 run_validations(:validate_on_create)
157 validate_on_create
158 else
159 run_validations(:validate_on_update)
160 validate_on_update
161 end
162
163 errors.empty?
164 end
165
166 # Returns the Errors object that holds all information about attribute error messages.
167 def errors
168 @errors ||= Errors.new(self)
169 end
170
171 protected
172 # Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.
173 def validate #:doc:
174 end
175
176 # Overwrite this method for validation checks used only on creation.
177 def validate_on_create #:doc:
178 end
179
180 # Overwrite this method for validation checks used only on updates.
181 def validate_on_update # :doc:
182 end
183
184 private
185 def run_validations(validation_method)
186 validations = self.class.read_inheritable_attribute(validation_method.to_sym)
187 if validations.nil? then return end
188 validations.each do |validation|
189 if validation.is_a?(Symbol)
190 self.send(validation)
191 elsif validation.is_a?(String)
192 eval(validation, binding)
193 elsif validation_block?(validation)
194 validation.call(self)
195 elsif validation_class?(validation, validation_method)
196 validation.send(validation_method, self)
197 else
198 raise(
199 ActiveRecordError,
200 "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " +
201 "class implementing a static validation method"
202 )
203 end
204 end
205 end
206
207 def validation_block?(validation)
208 validation.respond_to?("call") && (validation.arity == 1 || validation.arity == -1)
209 end
210
211 def validation_class?(validation, validation_method)
212 validation.respond_to?(validation_method)
213 end
214 end
215end