Errors
Last updated
Last updated
All Futures supports the same error handling pipeline as Active Record, errors
, which is an enumerable instance of ActiveModel::Errors
.
When validations fail, ActiveModel::Error
instances will be added to the errors.objects
Array. objects
is actually just an alias for errors.errors
, which is just not pretty enough for Rails.
In the interest of brevity and readability, the receiver and errors
object have been omitted from every reference to a method in this chapter.
When you read full_messages_for :name
, it is a stand-in for record.errors.full_messages_for :name
.
When a model is initialized, its objects
Array is empty until you either call save
/ update
or invoke valid?
directly. valid?
returns false
if at least one validation failed, and there will now be at least one ActiveModel::Error
in the objects
Array.
The valid?
method clears the objects
Array, which means that adding errors will not make a model invalid. Instead, it's failed validations that typically add the errors.
Our goal is to respond to this invalid state as part of the normal user experience, without actually raising an application level exception. To achieve this "successful failure", Rails gives us a family of methods that operate on the objects
Array.
Many Rails developers think of errors
as "the thing generated resources use to show validation failure messages". All Futures offers developers several compelling reasons to learn what ActiveModel::Errors
has to offer someone building a reactive UI.
Let's start with a basic assumption: if your model is valid?
- that is, true
- then you shouldn't have any errors.
However, you might not want to run valid?
because it runs all of your validations again, and that might be undesirable. The valid_email2 gem actually tests for valid MX servers, which could be slow. Or perhaps your validations connect with a paid API? (Don't actually do this!)
You can use the any?
method to check for the presence of errors in the objects
Array, and size
to get a count. You can use attribute_names
to access an Array of Symbols representing - you guessed it - attribute names that have errors associated with them.
There's also a details
method that returns a Hash structure which conveys all error types for all attributes, eg. {:name=>[{:error=>:blank}]}
objects
is an Array, so you should be able to just add and remove Error objects, right?
Actually, no - we don't want to manually instantiate ActiveModel::Error
objects. Instead, we can make use of add
, delete
which make it easy to work with errors, even across multiple versions of Rails when the internal structure of Error
objects might change over time.
You can clear
your objects
Array, but that won't make your model valid. To do that, you have to call valid?
again - hopefully with the errors fixed.
full_messages
vs messages
The error messages generated by ActiveModel::Errors
are available with and without the attribute name prefixed, offering you a choice between "Name is invalid" (full_messages
) and "is invalid" (messages
).
Both are useful in different situations; you might not want an awkwardly-named attribute being converted into a proper noun, such as "State Province can't be empty".
The key to enlightenment is to spend time studying (and potentially modifying) the locale files for the languages that you support. Of course, you can also specify messages on a per-validation basis, but that can add complexity to your internationalization strategy.
The payoff for this chapter is that you can access the errors for a specific attribute, or for the :base
model instance. When paired with All Future's ability to tell you if a given attribute is valid - even when the model itself might not be - this is a major level-up for reactive UI developers looking to give real-time feedback on an input field.
You can pass an attribute name as a Symbol to include?
(aliased as both key?
and has_key?
) and receive a Boolean indicating whether that attribute has at least one error.
Pass a Symbol to messages_for(:attribute)
or full_messages_for(:attribute)
and get the errors for that attribute.
You can also access the ActiveModel::Error
instances using the where
method:
To get the first full_message
error for :name
you would do something like this:
Most applications have two exception layers: the "successful failure" that comes from processing user input within predetermined constraints, and everything else. You need to determine which category of error that you're dealing with so you remain in control of the user experience, even when things go wrong.
There's actually two methods that test for the presence of specific errors. In the beginning, there was added?
, while of_kind?
was added in Rails 6.
added?
is designed to use the same syntax as the add
method. It matches against the specific options used to generate an error, or the final string generated by the error. It will not detect errors that do not match the exact signature of the query.
of_kind?
is much more forgiving, in that it only accepts the attribute and error type but will return true
if that error occurs, regardless of the options.
We find it confusing that there are two methods for this, too. Think of it like a John Hughes movie: added?
is the uptight math teacher, while of_kind?
is the coach who doles out tough love, but won't rat you out to the Vice Principal.