原创作者: hideto
阅读:1545次
评论:0条
更新时间:2011-05-26
Validations相关的源码全在validations.rb文件里:
从上述代码我们可以看出以下几点:
1,save、save!方法会使用validation,而update_attribute则skip validation
2,record.errors方法得到该record经过validate后的所有errors
3,save(false)会skip validation
overwriting Base#validate的例子:
view中:
或者
看了validations的代码,我们甚至可以写自己的Validation关键字:
这个例子使用FAUtils库来验证oakid的合法性,一切都很简单吧?
module ActiveRecord class Errors include Enumerable @@default_error_messages = { :inclusion => "is not included in the list", :exclusion => "is reserved", :invalid => "is invalid", :confirmation => "doesn't match confirmation", :accepted => "must be accepted", :empty => "can't be empty", :blank => "can't be blank", :too_long => "is too long (maximum is %d characters)", :too_short => "is too short (minimum is %d characters)", :wrong_length => "is the wrong length (should be %d characters)", :taken => "has already been taken", :not_a_number => "is not a number" } def add(attribute, msg = @@default_error_messages[:invalid]) @errors[attribute.to_s] = [] if @errors[attribute.to_s].nil? @errors[attribute.to_s] << msg end def add_on_empty(attributes, msg = @@default_error_messages[:empty]) for attr in [attributes].flatten value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s] is_empty = value.respond_to?("empty?") ? value.empty? : false add(attr, msg) unless !value.nil? && !is_empty end end end module Validations def self.included(base) # :nodoc: base.extend ClassMethods base.class_eval do alias_method_chain :save, :validation alias_method_chain :save!, :validation alias_method_chain :update_attribute, :validation_skipping end end module ClassMethods def validate(*methods, &block) methods << block if block_given? write_inheritable_set(:validate, methods) end def validate_on_create(*methods, &block) methods << block if block_given? write_inheritable_set(:validate_on_create, methods) end def validate_on_update(*methods, &block) methods << block if block_given? write_inheritable_set(:validate_on_update, methods) end def validates_each(*attrs) options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {} attrs = attrs.flatten # Declare the validation. send(validation_method(options[:on] || :save)) do |record| # Don't validate when there is an :if condition and that condition is false unless options[:if] && !evaluate_condition(options[:if], record) attrs.each do |attr| value = record.send(attr) next if value.nil? && options[:allow_nil] yield record, attr, value end end end end def validates_confirmation_of(*attr_names) configuration = { :message => ActiveRecord::Errors.default_error_messages[:confirmation], :on => :save } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) attr_accessor *(attr_names.map { |n| "#{n}_confirmation" }) validates_each(attr_names, configuration) do |record, attr_name, value| record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_confirmation").nil? or value == record.send("#{attr_name}_confirmation") end end def validates_presence_of(*attr_names) configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) # can't use validates_each here, because it cannot cope with nonexistent attributes, # while errors.add_on_empty can attr_names.each do |attr_name| send(validation_method(configuration[:on])) do |record| unless configuration[:if] and not evaluate_condition(configuration[:if], record) record.errors.add_on_blank(attr_name,configuration[:message]) end end end end def validates_length_of(*attrs) # Merge given options with defaults. options = { :too_long => ActiveRecord::Errors.default_error_messages[:too_long], :too_short => ActiveRecord::Errors.default_error_messages[:too_short], :wrong_length => ActiveRecord::Errors.default_error_messages[:wrong_length] }.merge(DEFAULT_VALIDATION_OPTIONS) options.update(attrs.pop.symbolize_keys) if attrs.last.is_a?(Hash) # Ensure that one and only one range option is specified. range_options = ALL_RANGE_OPTIONS & options.keys case range_options.size when 0 raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.' when 1 # Valid number of options; do nothing. else raise ArgumentError, 'Too many range options specified. Choose only one.' end # Get range option and value. option = range_options.first option_value = options[range_options.first] case option when :within, :in raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range) too_short = options[:too_short] % option_value.begin too_long = options[:too_long] % option_value.end validates_each(attrs, options) do |record, attr, value| if value.nil? or value.split(//).size < option_value.begin record.errors.add(attr, too_short) elsif value.split(//).size > option_value.end record.errors.add(attr, too_long) end end when :is, :minimum, :maximum raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0 # Declare different validations per option. validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" } message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long } message = (options[:message] || options[message_options[option]]) % option_value validates_each(attrs, options) do |record, attr, value| if value.kind_of?(String) record.errors.add(attr, message) unless !value.nil? and value.split(//).size.method(validity_checks[option])[option_value] else record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value] end end end end alias_method :validates_size_of, :validates_length_of def validates_uniqueness_of(*attr_names) configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken], :case_sensitive => true } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) validates_each(attr_names,configuration) do |record, attr_name, value| if value.nil? || (configuration[:case_sensitive] || !columns_hash[attr_name.to_s].text?) condition_sql = "#{record.class.table_name}.#{attr_name} #{attribute_condition(value)}" condition_params = [value] else condition_sql = "LOWER(#{record.class.table_name}.#{attr_name}) #{attribute_condition(value)}" condition_params = [value.downcase] end if scope = configuration[:scope] Array(scope).map do |scope_item| scope_value = record.send(scope_item) condition_sql << " AND #{record.class.table_name}.#{scope_item} #{attribute_condition(scope_value)}" condition_params << scope_value end end unless record.new_record? condition_sql << " AND #{record.class.table_name}.#{record.class.primary_key} <> ?" condition_params << record.send(:id) end if record.class.find(:first, :conditions => [condition_sql, *condition_params]) record.errors.add(attr_name, configuration[:message]) end end end def validates_format_of(*attr_names) configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :with => nil } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) raise(ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash") unless configuration[:with].is_a?(Regexp) validates_each(attr_names, configuration) do |record, attr_name, value| record.errors.add(attr_name, configuration[:message]) unless value.to_s =~ configuration[:with] end end def validates_numericality_of(*attr_names) configuration = { :message => ActiveRecord::Errors.default_error_messages[:not_a_number], :on => :save, :only_integer => false, :allow_nil => false } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) if configuration[:only_integer] validates_each(attr_names,configuration) do |record, attr_name,value| record.errors.add(attr_name, configuration[:message]) unless record.send("#{attr_name}_before_type_cast").to_s =~ /\A[+-]?\d+\Z/ end else validates_each(attr_names,configuration) do |record, attr_name,value| next if configuration[:allow_nil] and record.send("#{attr_name}_before_type_cast").nil? begin Kernel.Float(record.send("#{attr_name}_before_type_cast").to_s) rescue ArgumentError, TypeError record.errors.add(attr_name, configuration[:message]) end end end end end def valid? errors.clear run_validations(:validate) validate if new_record? run_validations(:validate_on_create) validate_on_create else run_validations(:validate_on_update) validate_on_update end errors.empty? end def errors @errors ||= Errors.new(self) end end end
从上述代码我们可以看出以下几点:
1,save、save!方法会使用validation,而update_attribute则skip validation
2,record.errors方法得到该record经过validate后的所有errors
3,save(false)会skip validation
overwriting Base#validate的例子:
class Person < ActiveRecord::Base protected def validate errors.add_on_empty %w( first_name last_name ) errors.add("phone_number", "has invalid format") unless phone_number =~ /[0-9]*/ end def validate_on_create # is only run the first time a new object is saved unless valid_discount?(membership_discount) errors.add("membership_discount", "has expired") end end def validate_on_update errors.add_to_base("No changes have occurred") if unchanged_attributes? end end
view中:
<% unless @post.errors.empty? %> The post couldn't be saved due to these errors: <ul> <% @post.errors.each_full do |message| %> <li><%= message %></li> <% end %> </ul> <% end %>
或者
<%= error_messages_for 'post' %>
看了validations的代码,我们甚至可以写自己的Validation关键字:
module ActiveRecord module Validations module ClassMethods def validates_oak_id(*attr_names) configuration = { :message => "Invalid Oak ID" } configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash) validates_each (attr_names, configuration) do |record, attr_name, value| record.errors.add(attr_name, configuration[:message]) unless FAUtils.valid_oakid?(value) end end end end end
这个例子使用FAUtils库来验证oakid的合法性,一切都很简单吧?
评论 共 0 条 请登录后发表评论