Rails validations, Exceptions, and Error Handling
william-luck
Posted on January 26, 2023
What is the difference between .new/.save and .create, as well as their sister methods? (.save! and .create!) When should you use them and how do they relate to the validations in rails? How do you handle exceptions and invalid data when saving to a database?
This blog post assumes some level of rails validation code present in your models. The simplest of validations is that an attribute has to exist on the instance before saving it to the database. For example, I created an application that has a household (or family) as one of the models. For the household to be created, the only thing that has to be supplied from the user is the address of the household.
I can create a new instance of a household using `Household.new’ in the rails console. However, the return value of such is just an empty household instance without any attributes (all having nil values):
Even though I have the validations for address present in the Household model file, rails still allowed me to create the instance because only database activity triggers the validations. We are not attempting to save the household (yet) to the database. From the return value alone, there is not really a way to see if the instance could be saved to the database with these nil values. We could, however, run Household.valid?
to see if the data is valid:
The return value of this code is false, which indicates that the data is invalid. If I instead created a separate instance of a household while specifying the address, the data would be valid when saving to the database:
You can imagine a use for this boolean return values in your control flow, using conditional statements. In your controller CRUD actions, for example, you could run the .valid?
method before continuing, or return an error otherwise that you could customize to the needs of your application. Continuing on with the two above examples. What will happen if I attempt to save the household
instance to the database? Remember that validations trigger upon database activity, so rails will check for the presence of address.
Let’s start with the valid data:
Rails successfully saved our household
instance to the database, and calling Household.last
in the console will give us our newly created household. In my model, each household can also have a date of entry. However, since I did not include the validations for that attribute in my household model, the instance is still saved successfully in the database.
For the invalid household instance, when attempting to save it to the database, something else happens:
We get a return value of false
. Rails does not give us the reason for the valid data by default without some error handling. Calling household.create
on this instance and using the return value would serve the same purpose as calling household.valid?
for your control flow, but there’s much more room for error, especially if you were expecting the data to be valid and were then calling other methods on this false return value. Unfortunately, with the return value by itself, there’s not much we can tell the user or our program, other than the instance was invalid.
Having rails throw an exception would be more useful, along with an error message of exactly what went wrong when saving the instance to the database. Exceptions can be raised if we make use of .save!
and .create!
sister methods. .create
and .create!
combine the use of .new
and .save
, where rails attempts to create an instance of the household and save that instance to the database in one go. What happens if we use .create!
for a new household with invalid data (no address supplied)?
Rails will throw an RecordInvalid exception, along with a useful and simple error message of why the creation was unsuccessful: that the the address cannot be blank. But how do we plan for these errors and exceptions in our code? If we have multiple methods to create and update household data in the households controller, a useful way would be to include a rescue_from ActiveRecord::RecordInvalid
statement at the top of our controller, which would kick in if there are any RecordInvalid exceptions in any method, along with a custom class method to render JSON of the details of that error for our front end:
Using the above code, any time that there is an RecordInvalid exception, the render_invalid_data_error
method will return JSON with some nicely formatted errors to use in the front end. We can obtain an object of the errors in the frontend with invalid.record.errors
, and put those errors in an array with invalid.record.errors.full_messages
:
If the data was valid, my application would have returned JSON of the valid household from the server upon creation. Instead, a RecordInvalid exception was thrown, and the rescue_from statement along with the custom method returned JSON of the error itself, formatted into an array. Moreover, notice the 422 error code (unprocessable entity) returned from the server, which we specified with status: :unprocessable_entity’ in the
render_invalid_data_errors` method.
These different error codes are helpful for control flow, as we have Javascript in the front end determine if the response was successful, and have our application perform different tasks depending on the error code of the response. For example, if the household creation was successful, I can set state with the details of the newly created household in React, or I could set state for front end error messages otherwise:
In the else
statement, I set state for the errors, which is the array of error(s) returned from the server. I can then display these errors in an alert message to the user, letting them know exactly what wrong, and what to fix in the form data before continuing and being pushed to other areas of the application.
Posted on January 26, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.