Permalink
Browse files

Move all the Active Record validations to Active Model

  • Loading branch information...
1 parent 6ed42eb commit 8828b2ca674acfa028a3c1e086a1795d3bb893e1 @lifo lifo committed Mar 19, 2009
@@ -1,5 +1,43 @@
-require 'active_model/observing'
-# disabled until they're tested
-# require 'active_model/callbacks'
-# require 'active_model/validations'
-require 'active_model/base'
+#--
+# Copyright (c) 2004-2009 David Heinemeier Hansson
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#++
+
+begin
+ require 'active_support'
+rescue LoadError
+ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib"
+ if File.directory?(activesupport_path)
+ $:.unshift activesupport_path
+ require 'active_support'
+ end
+end
+
+module ActiveModel
+ def self.load_all!
+ [Base]
+ end
+
+ autoload :Base, 'active_model/base'
+ autoload :Validations, 'active_model/validations'
+ autoload :Errors, 'active_model/errors'
+ autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
+end
@@ -1,37 +1,28 @@
module ActiveModel
module DeprecatedErrorMethods
def on(attribute)
- ActiveSupport::Deprecation.warn "Errors#on have been deprecated, use Errors#[] instead"
- self[attribute]
+ # ActiveSupport::Deprecation.warn "Errors#on have been deprecated, use Errors#[] instead"
+ errors = self[attribute]
+ errors.size < 2 ? errors.first : errors
end
def on_base
- ActiveSupport::Deprecation.warn "Errors#on_base have been deprecated, use Errors#[:base] instead"
+ # ActiveSupport::Deprecation.warn "Errors#on_base have been deprecated, use Errors#[:base] instead"
on(:base)
end
- def add(attribute, msg = Errors.default_error_messages[:invalid])
- ActiveSupport::Deprecation.warn "Errors#add(attribute, msg) has been deprecated, use Errors#[attribute] << msg instead"
- self[attribute] << msg
- end
-
def add_to_base(msg)
- ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#[:base] << msg instead"
+ # ActiveSupport::Deprecation.warn "Errors#add_to_base(msg) has been deprecated, use Errors#[:base] << msg instead"
self[:base] << msg
end
def invalid?(attribute)
- ActiveSupport::Deprecation.warn "Errors#invalid?(attribute) has been deprecated, use Errors#[attribute].any? instead"
+ # ActiveSupport::Deprecation.warn "Errors#invalid?(attribute) has been deprecated, use Errors#[attribute].any? instead"
self[attribute].any?
end
- def full_messages
- ActiveSupport::Deprecation.warn "Errors#full_messages has been deprecated, use Errors#to_a instead"
- to_a
- end
-
def each_full
- ActiveSupport::Deprecation.warn "Errors#each_full has been deprecated, use Errors#to_a.each instead"
+ # ActiveSupport::Deprecation.warn "Errors#each_full has been deprecated, use Errors#to_a.each instead"
to_a.each { |error| yield error }
end
end
@@ -1,40 +1,27 @@
module ActiveModel
class Errors < Hash
include DeprecatedErrorMethods
-
- @@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",
- :greater_than => "must be greater than %d",
- :greater_than_or_equal_to => "must be greater than or equal to %d",
- :equal_to => "must be equal to %d",
- :less_than => "must be less than %d",
- :less_than_or_equal_to => "must be less than or equal to %d",
- :odd => "must be odd",
- :even => "must be even"
- }
-
- ##
- # :singleton-method:
- # Holds a hash with all the default error messages that can be replaced by your own copy or localizations.
- cattr_accessor :default_error_messages
+
+ class << self
+ def default_error_messages
+ message = "Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages')."
+ ActiveSupport::Deprecation.warn(message)
+
+ I18n.translate 'activerecord.errors.messages'
+ end
+ end
+
+ def initialize(base)
+ @base = base
+ super()
+ end
alias_method :get, :[]
alias_method :set, :[]=
def [](attribute)
if errors = get(attribute.to_sym)
- errors.size == 1 ? errors.first : errors
+ errors
else
set(attribute.to_sym, [])
end
@@ -55,17 +42,11 @@ def size
end
def to_a
- inject([]) do |errors_with_attributes, (attribute, errors)|
- if error.blank?
- errors_with_attributes
- else
- if attr == :base
- errors_with_attributes << error
- else
- errors_with_attributes << (attribute.to_s.humanize + " " + error)
- end
- end
- end
+ full_messages
+ end
+
+ def count
+ to_a.size
end
def to_xml(options={})
@@ -78,5 +59,106 @@ def to_xml(options={})
to_a.each { |error| e.error(error) }
end
end
+
+ # Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
+ # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
+ # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
+ # If no +messsage+ is supplied, :invalid is assumed.
+ # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
+ def add(attribute, message = nil, options = {})
+ message ||= :invalid
+ message = generate_message(attribute, message, options) if message.is_a?(Symbol)
+ self[attribute] << message
+ end
+
+ # Will add an error message to each of the attributes in +attributes+ that is empty.
+ def add_on_empty(attributes, custom_message = nil)
+ [attributes].flatten.each do |attribute|
+ value = @base.respond_to?(attribute.to_s) ? @base.send(attribute.to_s) : @base[attribute.to_s]
+ is_empty = value.respond_to?(:empty?) ? value.empty? : false
+ add(attribute, :empty, :default => custom_message) unless !value.nil? && !is_empty
+ end
+ end
+
+ # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
+ def add_on_blank(attributes, custom_message = nil)
+ [attributes].flatten.each do |attribute|
+ value = @base.respond_to?(attribute.to_sym) ? @base.send(attribute.to_sym) : @base[attribute.to_sym]
+ add(attribute, :blank, :default => custom_message) if value.blank?
+ end
+ end
+
+ # Returns all the full error messages in an array.
+ #
+ # class Company < ActiveRecord::Base
+ # validates_presence_of :name, :address, :email
+ # validates_length_of :name, :in => 5..30
+ # end
+ #
+ # company = Company.create(:address => '123 First St.')
+ # company.errors.full_messages # =>
+ # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
+ def full_messages(options = {})
+ full_messages = []
+
+ each do |attribute, messages|
+ next if messages.empty?
+
+ if attribute == :base
+ messages.each {|m| full_messages << m }
+ else
+ attr_name = @base.class.human_attribute_name(attribute.to_s)
+ prefix = attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ')
+ messages.each do |m|
+ full_messages << "#{prefix}#{m}"
+ end
+ end
+ end
+
+ full_messages
+ end
+
+ # Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
+ # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there,
+ # it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the
+ # default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name,
+ # translated attribute name and the value are available for interpolation.
+ #
+ # When using inheritence in your models, it will check all the inherited models too, but only if the model itself
+ # hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
+ # error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
+ #
+ # <ol>
+ # <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
+ # <li><tt>activerecord.errors.models.admin.blank</tt></li>
+ # <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
+ # <li><tt>activerecord.errors.models.user.blank</tt></li>
+ # <li><tt>activerecord.errors.messages.blank</tt></li>
+ # <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
+ # </ol>
+ def generate_message(attribute, message = :invalid, options = {})
+ message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
+
+ defaults = @base.class.self_and_descendants_from_active_record.map do |klass|
+ [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
+ :"models.#{klass.name.underscore}.#{message}" ]
+ end
+
+ defaults << options.delete(:default)
+ defaults = defaults.compact.flatten << :"messages.#{message}"
+
+ key = defaults.shift
+ value = @base.respond_to?(attribute) ? @base.send(attribute) : nil
+
+ options = { :default => defaults,
+ :model => @base.class.human_name,
+ :attribute => @base.class.human_attribute_name(attribute.to_s),
+ :value => value,
+ :scope => [:activerecord, :errors]
+ }.merge(options)
+
+ I18n.translate(key, options)
+ end
+
end
end
@@ -38,7 +38,7 @@ module ClassMethods
# end
#
# This usage applies to +validate_on_create+ and +validate_on_update as well+.
- #
+
# Validates each attribute against a block.
#
# class Person < ActiveRecord::Base
@@ -48,7 +48,7 @@ module ClassMethods
# end
#
# Options:
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>)
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
@@ -83,7 +83,7 @@ def validation_method(on)
# Returns the Errors object that holds all information about attribute error messages.
def errors
- @errors ||= Errors.new
+ @errors ||= Errors.new(self)
end
# Runs all the specified validations and returns true if no errors were added otherwise false.
@@ -92,35 +92,52 @@ def valid?
run_callbacks(:validate)
- if responds_to?(:validate)
- ActiveSupport::Deprecations.warn "Base#validate has been deprecated, please use Base.validate :method instead"
+ if respond_to?(:validate)
+ # ActiveSupport::Deprecation.warn "Base#validate has been deprecated, please use Base.validate :method instead"
validate
end
if new_record?
run_callbacks(:validate_on_create)
- if responds_to?(:validate_on_create)
- ActiveSupport::Deprecations.warn(
- "Base#validate_on_create has been deprecated, please use Base.validate_on_create :method instead")
+ if respond_to?(:validate_on_create)
+ # ActiveSupport::Deprecation.warn "Base#validate_on_create has been deprecated, please use Base.validate_on_create :method instead"
validate_on_create
end
else
run_callbacks(:validate_on_update)
- if responds_to?(:validate_on_update)
- ActiveSupport::Deprecations.warn(
- "Base#validate_on_update has been deprecated, please use Base.validate_on_update :method instead")
+ if respond_to?(:validate_on_update)
+ # ActiveSupport::Deprecation.warn "Base#validate_on_update has been deprecated, please use Base.validate_on_update :method instead"
validate_on_update
end
end
errors.empty?
end
+
+ # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
+ def invalid?
+ !valid?
+ end
+
+ protected
+
+ # Overwrite this method for validation checks on all saves and use <tt>Errors.add(field, msg)</tt> for invalid attributes.
+ def validate
+ end
+
+ # Overwrite this method for validation checks used only on creation.
+ def validate_on_create
+ end
+
+ # Overwrite this method for validation checks used only on updates.
+ def validate_on_update
+ end
end
end
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
filename = File.basename(path)
require "active_model/validations/#{filename}"
-end
+end
Oops, something went wrong.

0 comments on commit 8828b2c

Please sign in to comment.