There are several places outside of the standard validations where you might process user input in a Rails model and want to inform the user that they supplied bad data. It is not immediately obvious though how you get useful error messages back to the user. Rails gives your model an instance of
ActiveRecord::Errors called errors, which is the standard way to return validation errors. You have to think of any other user input checking as validation and somehow get error messages into the errors object.
Without further ado, from easiest to hardest:
Standard Validations
With a standard validation Rails handles adding an appropriate error message to errors for you.
class Thing < ActiveRecord::Base
validates :name, presence: true
end
Custom Validations
To quote from the
Rails guide to custom validation:
"You can also create methods that verify the state of your models and add messages to the errors collection when they are invalid."
class Thing < ActiveRecord::Base
validate :custom_validator
def custom_validator
errors[:name] << 'This is a custom validation error on name'
end
end
Before Validation Callback
Rails provides callbacks at a
number of stages of model processing.
To quote from the
Rails guide regarding halting execution in a callback:
"If any before callback method returns exactly false or raises an exception, the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception."
"Raising an arbitrary exception may break code that expects save and its friends not to fail like that. The ActiveRecord::Rollback exception is thought precisely to tell Active Record a rollback is going on. That one is internally captured but not reraised."
It doesn't mention that before_validate callbacks have another option.
Specialized validation should normally go in a custom validator, but if for some reason you have to perform a check in a callback then any time you are processing user input you should do that in a before_validate callback. Using before_validate allows you to use the standard errors object, which behaves just like it does in a custom validator.
class Thing < ActiveRecord::Base
before_validation :bv
def bv
errors[:name] << 'This is a before validation error on name'
end
end
Virtual Attribute
You can define
virtual attributes in a model. As shown in the RailsCast you can use custom validation for simple cases. But some virtual attributes may be doing complex processing that causes you to discover errors in the user input inside the virtual attribute method. Assignment methods are called before validation. They're even called
before the before_initialization callback for any virtual attributes you pass via new/create.
Apparently this is too early in the process to use the standard errors object, anything added to errors is ignored. But since these methods execute before validation we can just save up any errors and insert them into the standard errors object during the validation stage.
class Thing < ActiveRecord::Base
def virtual=(virtual_value)
@virtual_errors = {name: ['Virtual is invalid']}
end
before_validation :bv
def bv
@virtual_errors.each do |k,v|
v.each { |e| errors[k] << e }
end
end
end