Errors
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.class Teenager < AllFutures::Base
validate :designated_driver?
def :designated_driver?
errors.add(:base, "is drunk") unless sober?
end
end
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?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.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.
where(:name) # all name errors
where(:name, :too_short) # all name errors being too short
where(:name, :too_short, minimum: 2) # all name errors being too short and minimum is 2
To get the first
full_message
error for :name
you would do something like this:record.errors.where(:name).first.full_message # => "Name can't be blank"
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.
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.Last modified 1yr ago