require 'active_support/core_ext/integer/even_odd'
module ActiveRecord
# Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the
# +record+ method to retrieve the record which did not validate.
# begin
# complex_operation_that_calls_save!_internally
# rescue ActiveRecord::RecordInvalid => invalid
# puts invalid.record.errors
# end
class RecordInvalid < ActiveRecordError
attr_reader :record
def initialize(record)
@record = record
super("Validation failed: #{@record.errors.full_messages.join(", ")}")
end
end
class Errors < ActiveModel::Errors
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
# 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|
messages = Array.wrap(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
module Validations
extend ActiveSupport::Concern
include ActiveSupport::Callbacks
include ActiveModel::Validations
included do
alias_method_chain :save, :validation
alias_method_chain :save!, :validation
define_callbacks :validate_on_create, :validate_on_update
end
module ClassMethods
# Creates an object just like Base.create but calls save! instead of save
# so an exception is raised if the record is invalid.
def create!(attributes = nil, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create!(attr, &block) }
else
object = new(attributes)
yield(object) if block_given?
object.save!
object
end
end
def validation_method(on)
case on
when :create
:validate_on_create
when :update
:validate_on_update
else
:validate
end
end
end
module InstanceMethods
# The validation process on save can be skipped by passing false. The regular Base#save method is
# replaced with this when the validations module is mixed in, which it is by default.
def save_with_validation(perform_validation = true)
if perform_validation && valid? || !perform_validation
save_without_validation
else
false
end
end
# Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
# if the record is not valid.
def save_with_validation!
if valid?
save_without_validation!
else
raise RecordInvalid.new(self)
end
end
# Runs all the specified validations and returns true if no errors were added otherwise false.
def valid?
errors.clear
run_callbacks(:validate)
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 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 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
# Returns the Errors object that holds all information about attribute error messages.
def errors
@errors ||= Errors.new(self)
end
def get_attribute_value(attribute)
respond_to?(attribute.to_sym) ? send(attribute.to_sym) : self[attribute.to_sym]
end
end
end
end
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
filename = File.basename(path)
require "active_record/validations/#{filename}"
end