So let's assume that we've got our Rails API all set up, our models in place, and everything is working hunky-dory. Except for when it isn't. Our validations are set up, and they definitely work...but how do we know??
Server-Side Validations define how our application communicates with the server and determines what data is returned. So, let's explore how these validations are sent to the controller and displayed on the client-side.
Through our controller actions we can use the method .valid?
inside of a conditional if...else
statement to determine whether or not our program should render json data, or an error message. We can think of it like this:
- Before saving an Active Record object, Rails runs our validations. If these validations produce any errors, Rails does not save the object.
- We can also run these validations on our own. valid? triggers our validations and returns true if no errors were found in the object, and false otherwise.
-
invalid? is the inverse of
valid?
, it triggers our validations, returning true if any errors were found in the object, and false otherwise.
-
invalid? is the inverse of
The create
action is now saying…
-
if
the parameters that the user passed through as input values tobook_params
are valid andbook.valid?
evaluates astrue
- then render the object as created and persist the data to our database.
-
else
if the parameters did not pass our validation, andbook.valid?
evaluates asfalse
- then the application returns all of the error message as an array of strings
- and sets the status code to
422 :unprocessable_entity
The thing that was tough to grasp as a beginner was, using the if...else statement above does not persist our data to the database when a validation fails, but instead returns a nil object, or an empty object that looks like this:
=> #<Book id: nil, title: nil, author: nil, description: nil, page_count: nil, year: nil, created_at: nil, updated_at: nil>
The important thing to identify here is id: nil. Other values may be different, but id: nil tells us that this object did not receive an id when it was created, so its data has not persisted to our database.
…which is confusing. Because even though our instance did in fact fail our validations, we still received what looks like a valid object as a return value. How are you supposed to know if it passed? Check every single id??? Of course not, silly goose. That’s where .create!() comes in to rescue us from nil objects!
.create!() does not create an instance object with nil values, but instead raises an exception, or a return value that includes all our validation error messages, which looks like:
**ActiveRecord::RecordInvalid (Validation failed: Title can't be blank, Author can't be blank, Description can't be blank, Page Count can't be blank, Year can't be blank, Page count is not a number, Year is not a number)**
This return helps us to identify that our instance failed by returning error codes instead of an object that looks like it’s valid, but isn’t.
We can use rescue
from within our create controller action to render the exception’s error message as a JSON response
ALTERNATIVELY we can refactor and make this cleaner by using the method rescue_from to give specific instructions to our application for what to do when any exception is raised within our model, for any controller action.
We identify the exception using a specified name (i.e. ActiveRecord::RecordInvalid) and associating a private method called a validation helper and with:
Things to note:
-
.record
is a built-in method, used to retrieve the record which did not validate. -
.errors
is a built-in method, through which we can drill down into the various details of each error. After Active Record has performed validations, any errors found can be accessed through the errors instance method.- Returns a collection of errors.
- An object is
valid
if the returned collection is empty.
Using method-chaining with .record.errors
renders our parameter names (:title, :author, ...
) and their associated error messages in JSON format. It also sets the status code to 422: :unprocessable_entity
Here, our helper method is render_unprocessable_entity()
and takes in a single argument; an object that contains all of the errors that were triggered by the newly created class instance, called an exception.
Client-side routing is different than server-side routing - the customer experience is very very different compared to what the chef is doing - though they are, in an abstract way, still connected, they are not directly dependent on one another.
In order to get our React App to render our error responses, we need to modify the fetch request. For example,
Note: response.ok
contains a boolean value stating whether the response was successful (status in the range 200-299) or not.
We have to use Object.entries
to create our mappable array in this case because of how our errors
data is nested inside of an array, inside of an object, inside of a hash.
-
Data is nested in a hash with errors listed individually, and their messages as arrays…
{errors: {”title”: [”can’t be blank”]}, {"author": ["can't be blank"]}, ...}
Because of this, if we call
Object.entries
on our errors, it turns our content into an array of arrays with the key of a particular value in the first index and the error message in a nested array that looks like:
Object.entries(data.errors) -->
[[title, ["can't be blank"]], [author, ["can't be blank"]], [page_count, ["can't be blank", "is not a number"]]...]
- With this nested array, we can call
.map
on each individual entry and use string interpolation to create an array of errors as strings that we can map through, using their index.
.map(error => `${error[0]}: ${error[1]}`)
errors = ["title: can't be blank", "author: can't be blank", "page_count: can't be blank,is not a number"...]
- We can then use a conditional ternary statement within our component to display the error messages, if any exist.
- This newly created
errors
array is then set to state using oursetErrors
function, which causes our component to re-render - When the component re-renders, it checks the
errors
state array to see if there’s anything inside of it - If there is, it renders each item in the
errors
array inside of a<div>
element
And that's all there is for errors! From server to client side, hopefully that all makes some kind of sense.
If you have any questions or recommendations for edits, feel free to reach out. I welcome any and all feedback.
Keep fightin' the good fight! ʕ•ᴥ•ʔ
Top comments (0)