Efficient way to report record validation warnings as well as errors?
Here is some code I wrote for a Rails 3 project that does exactly what you're talking about here.
# Define a "warnings" validation bucket on ActiveRecord objects.
#
# @example
#
# class MyObject < ActiveRecord::Base
# warning do |vehicle_asset|
# unless vehicle_asset.description == 'bob'
# vehicle_asset.warnings.add(:description, "should be 'bob'")
# end
# end
# end
#
# THEN:
#
# my_object = MyObject.new
# my_object.description = 'Fred'
# my_object.sensible? # => false
# my_object.warnings.full_messages # => ["Description should be 'bob'"]
module Warnings
module Validations
extend ActiveSupport::Concern
include ActiveSupport::Callbacks
included do
define_callbacks :warning
end
module ClassMethods
def warning(*args, &block)
options = args.extract_options!
if options.key?(:on)
options = options.dup
options[:if] = Array.wrap(options[:if])
options[:if] << "validation_context == :#{options[:on]}"
end
args << options
set_callback(:warning, *args, &block)
end
end
# Similar to ActiveModel::Validations#valid? but for warnings
def sensible?
warnings.clear
run_callbacks :warning
warnings.empty?
end
# Similar to ActiveModel::Validations#errors but returns a warnings collection
def warnings
@warnings ||= ActiveModel::Errors.new(self)
end
end
end
ActiveRecord::Base.send(:include, Warnings::Validations)
The comments at the top show how to use it. You can put this code into an initializer and then warnings should be available to all of your ActiveRecord objects. And then basically just add a warnings do
block to the top of each model that can have warnings and just manually add as many warnings as you want. This block won't be executed until you call .sensible?
on the model.
Also, note that since warnings are not validation errors, a model will still be technically valid even if it isn't "sensible" (as I called it).
Years later, but in newer Rails versions there's a bit easier way:
attr_accessor :save_despite_warnings
def warnings
@warnings ||= ActiveModel::Errors.new(self)
end
before_save :check_for_warnings
def check_for_warnings
warnings.add(:notes, :too_long, count: 120) if notes.to_s.length > 120
!!save_despite_warnings
end
Then you can do: record.warnings.full_messages
Another options is to have a seperate object for warnings as such:
class MyModelWarnings < SimpleDelegator
include ActiveModel::Validations
validates :name, presence: true
def initialize(model)
super
validate
end
def warnings; errors; end
end
class MyModel < ActiveRecord::Base
def warnings
@warnings ||= MyModelWarnings.new(self).warnings
end
end