module Recliner
# Raised by save! and create! when the document is invalid. Use the
# +document+ method to retrieve the document which did not validate.
# begin
# complex_operation_that_calls_save!_internally
# rescue Recliner::DocumentInvalid => invalid
# puts invalid.document.errors
# end
class DocumentInvalid < DocumentNotSaved
attr_reader :document
def initialize(document)
@document = document
super("Validation failed: #{@document.errors.full_messages.join(", ")}")
end
end
class Errors < ActiveModel::Errors
# Returns all the full error messages in an array.
#
# class Company < Recliner::Document
# 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('recliner.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 (recliner.errrors.messages).
# Error messages are first looked up in models.MODEL.attributes.ATTRIBUTE.MESSAGE, if it's not there,
# it's looked up in models.MODEL.MESSAGE and if that is not there it returns the translation of the
# default message (e.g. recliner.errors.messages.MESSAGE). The translated model name,
# translated attribute name and the value are available for interpolation.
#
# When using inheritance in your models, it will check all the inherited models too, but only if the model itself
# hasn't been found. Say you have class Admin < User; end and you wanted the translation for the :blank
# error +message+ for the title +attribute+, it looks for these translations:
#
#
# - recliner.errors.models.admin.attributes.title.blank
# - recliner.errors.models.admin.blank
# - recliner.errors.models.user.attributes.title.blank
# - recliner.errors.models.user.blank
# - recliner.errors.messages.blank
# - any default you provided through the +options+ hash (in the recliner.errors scope)
#
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_recliner.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 => [:recliner, :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
# The validation process on save can be skipped by passing false. The regular Document#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 document just like Document#save but will raise a DocumentInvalid exception
# instead of returning false if the document is not valid.
def save_with_validation!
if valid?
save_without_validation!
else
raise DocumentInvalid.new(self)
end
end
# Runs all the specified validations and returns true if no errors were added otherwise false.
def valid?
errors.clear
@_on_validate = new_record? ? :create : :update
_run_validate_callbacks
errors.empty?
end
# Returns the Errors object that holds all information about attribute error messages.
def errors
@errors ||= Errors.new(self)
end
end
end
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
filename = File.basename(path)
require "recliner/validations/#{filename}"
end