DEV Community

Cover image for ERRORS: Bang, Rescue, Repeat.
WizardofHause
WizardofHause

Posted on

ERRORS: Bang, Rescue, Repeat.

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.

Example if...else

The create action is now saying…

  • if the parameters that the user passed through as input values to book_params are valid and book.valid? evaluates as true
    • then render the object as created and persist the data to our database.
  • else if the parameters did not pass our validation, and book.valid? evaluates as false
    • 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>
Enter fullscreen mode Exit fullscreen mode

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)**
Enter fullscreen mode Exit fullscreen mode

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

rescue Example

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:

rescue_from example

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,

POST 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"]]...]
Enter fullscreen mode Exit fullscreen mode
  • 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"...]
Enter fullscreen mode Exit fullscreen mode
  • 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 our setErrors 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)