Lesson Weekend

Our API now handles requests from users but doesn't provide much of a response for many of our endpoints. This isn't an issue with a successful GET call, which returns one or all of the quotes from the database, but it's problematic with other routes. Let's take a look at a few examples. We'll start by handling exceptions.

Handling Exceptions


What happens if a user attempts to get a record with an ID that doesn't exist? Try it out with Postman. The response is 500 lines of error messages formatted as a JSON object. This is TMI for users. Let's simplify the response by adding an exception handler. We'll add the code to our application controller so it’s accessible to all controllers.

controllers/application_controller.rb
...
rescue_from ActiveRecord::RecordNotFound do |exception|
  json_response({ message: exception.message }, :not_found)
end
...

This code will literally "rescue" our application from the specified ActiveRecord::RecordNotFound error by throwing an exception. Exceptions are a common programming concept. They indicate that something has gone wrong in your application. Remember that everything is an object in Ruby, so an exception is an instance of the Exception class (or a descendent of that class). By default, a Ruby application terminates when there’s an exception, but we can create exception handlers so the code within the handler is run (instead of the default action).

In our case, we just want to provide a friendly error message for our users, but we could pass other code into our rescue block as well.

The first argument in our json_response method takes the exception message and turns it into a JSON string. The second argument (:not_found) is the HTTP status code for the API request. You should have a general familiarity with HTTP status codes since you'll use them often. Here's a list of Rails symbols for HTTP status codes.

Try sending a doomed GET request again. The response is much cleaner now:

{
  "message": "Couldn't find Quote with 'id'=136"
}

Let's take a look at another exception. What happens if a record isn't created or updated successfully? Let's find out.

First, add validations to your quote.rb model. A user should only be able to create a quotation that includes both an author and the quote content itself.

Now try to create a record that has an author but no content. This call should fail (you can confirm this by checking the rails console to see if the record exists in the database), but it's not clear from the response. First, the call returns a 201 Created Status code. That's misleading since the record wasn't successfully saved.

Here's an example JSON object returned from a failed POST request:

{
  "id": null,
  "content": null,
  "author": "Jane Austen"
}

This isn't so helpful, either. There's no error message and it's not clear that a validation stopped the record from being saved.

Let's fix the issue. First, let's change our create route so we're using a bang method.

controllers/quotes_controller.rb
def create
  @quote = Quote.create!(quote_params)
  json_response(@quote)
end

Uh, oh. We have another long error message when the POST method fails. Once again, we need to handle this with an exception.

Without the bang, our create method will return the (unsaved) object, which misleads our users. With create!, our method will raise exceptions. In other words, create! will return the exception here, not the unsaved object. You can test this further by making a failed POST call with and without the bang method.

This also applies to the update method; adding a bang raises exceptions when the call fails, as you’ll see if you test a PUT call with an empty value for a validated parameter.

We'll need to add code to handle this exception. This time, I'll let you write the code yourself. Here are a few clues:

First, you'll need the specific ActiveRecord error. There are two places you can find this:

  • You can check the response of the API call, since it returns the entire error.
  • You can check the console pane where the server is running, which also returns the error.

Finally, you'll need to decide which HTTP status code should be passed in for a failed POST request.

From now on, whenever you get a new error in your application, you should add an exception handler to deal with it.

Bonus: Refactor your code and put your exception handlers into a module. It's a little trickier than the last module you made; if you just move the code into the module, you’ll get the following error when the exception is raised: undefined method 'rescue_from'. Here's a hint: check out ActiveSupport::Concern.

Success Messages


We've spent the first half of this lesson focusing on failure. Now let's focus on success.

We'll start with a POST method that hits our create route. If we make a successful POST call, our create route returns the saved object. That's actually useful, since our users can verify that they’ve properly posted the correct object. However, the HTTP status code is a bit vague; the call returns a 200 OK status. Yes, that means success, but there's an HTTP status code that's even more specific: 201 created.

We can quickly fix this issue. Let's modify our create route again:

controllers/quotes_controller.rb
def create
  @quote = Quote.create!(quote_params)
  json_response(@quote, :created)
end

Here we're passing in the Rails symbol for the relevant HTTP code just as we did in our exception handler.

Let's address another use case in our application. What happens when a quote is updated? The updated object is returned along with a200 OK status code. We should return a more specific message to the user when the PUT request is successful. Here’s how to add a message:

controllers/quotes_controller.rb
def update
  @quote = Quote.find(params[:id])
  if @quote.update!(quote_params)
    render status: 200, json: {
     message: "This quote has been updated successfully."
     }
  end
end

We bypass our json_response method and use Rails' built-in render method to pass in a status code and a JSON message. The response to the API call will now be much clearer to users.

Go ahead and add a success message to your destroy route as well.

Technically, you could also handle errors with conditionals and JSON messages, so why don’t we do this in our applications? Think back to that long list of HTTP status codes. We might need to add many or most of them to a complex API. Our controllers would get bloated very quickly if we added conditionals for each potential success and exception message.

Now that you know how to handle both exceptions and successful calls in your application, make sure you have clear responses for each possible request a user can make. This is an essential part of a well-coded, well-documented API. It also help users debug when they run into issues making requests.

Adding an Exception Handler

controllers/application_controller.rb
rescue_from ActiveRecord::RecordNotFound do |exception|
  json_response({ message: exception.message }, :not_found)
end

Passing in a Success Message

controllers/quotes_controller.rb
def update
  if @quote.update!(quote_params)
    render status: 200, json: {
     message: "Your quote has successfully been updated."
     }
  end
end

Lesson 6 of 19
Last updated more than 3 months ago.