Exception Handling and Validations in Rails, and how to display errors to users.
Tags: webdev, rails, beginners
Published: 2023-04-28 15:00:19

#Introduction

In this article we will go over exception handling and validation in Rails. Exception handling and validation are crucial for _any_ web application, including those built with Rails; it's how we're able to display error messages that are useful not only to us as developers, but to our application users as well. How would you know if you entered the wrong username or password in an application without any validation or error handling?

**First, a refresher(or introduction) on exceptions if you need it. Otherwise, skip ahead to the Validation section!**

---

#What is an exception?
You've already seen exceptions in action. For example, if you've ever made a typo in your code causing your program to crash with 'NoMethodError' or 'SyntaxError', you're seeing an exception being raised:

```ruby
putss 'Hello World'
# => NoMethodError: undefined method 'putss' for main:Object
# => Did you mean? puts
```

An exception represents an error condition in a program. Exceptions are how Ruby deals with unexpected events, and they provide a mechanism for stopping the execution of a program. When an exception is raised, your program starts to shut down. If nothing halts that process, your program will eventually stop and print out an error message.

##Exception Handling

Crashing programs are no bueno. Normally we want to stop the program from crashing and react to the error. This is called "handling" (also known as "rescuing" or "catching" an exception.

The basic syntax looks something like this:

```ruby
#Imagine we write a function that would be called when an exception is raised.
def handle_exception
  puts "Got an exception, but I'm handling it!"
end

begin
  # Any exceptions in here...
rescue
  # ...will trigger this block of code
  handle_exception
end

# => "Got an exception, but I'm handling it!"

```

In this example, an exception is raised, but the program does not crash because it was "rescued". Instead of crashing, Ruby runs the code in the `rescue` block, which prints out a message. This is cool and all, but all this does is tell us that something went wrong, without telling us *what* went wrong.

Any information about what went wrong is going to be contained within an **exception object**.

##Exception Objects

Exception objects (E.g. SyntaxError or ArgumentError) are regular Ruby objects(subclasses of the [Exception Class](https://ruby-doc.org/core-2.5.1/Exception.html)). They contain information about "what went wrong" for the exception that was rescued.

To get an exception object, the `rescue` syntax is slightly different. In this example, we'll rescue from a [ZeroDivisionError](https://ruby-doc.org/core-2.5.1/ZeroDivisionError.html) exception class.

```ruby
#Rescues all errors, and assigns the exception object to an `error` variable
rescue => error

#Rescues only ZeroDivisionError and assigns the exception object to an `error` variable
rescue ZeroDivisionError => error
```

In the second example above, `ZeroDivisionError` is the class of the object in the `error` variable,`ZeroDivisionError` itself is a subclass of `StandardError`. The most standard error types in Ruby, such as `ArgumentError` and `NameError` are subclasses of `StandardError`. In fact, `StandardError` is the default exception for Ruby when an `Exception` subclass is not explicitly named. 

```ruby
begin
  do_something
rescue => error
  # This is the same as rescuing StandardError
  # and all of its subclasses
end
```
Now.. let's go back and take a closer look at the exception object for `ZeroDivisionError`

```ruby
begin
  # Any exceptions in here...
  1 / 0
rescue ZeroDivisionError => error
  puts "Exception Class: #{error.class}"
  puts "Exception Message: #{error.message}"
end

# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
```

Most Ruby exceptions contain a message detailing what went wrong.

##Triggering Your Own Exceptions
You can also trigger your own exceptions! The process of raising your own exceptions is called.. well.. "raising". You do so by calling the `raise` method.

When raising your own exceptions, you choose which type of exception to use. You can also set your own error message.

```ruby
begin
  #raises an ArgumentError with the message "You screwed up!"
  raise ArgumentError.new("You screwed up!")
rescue ArgumentError => error
  puts error.message
end

# => "You screwed up!"
```

##Creating Custom Exceptions
Ruby's built-in exceptions are great, but they are still limited in scope and cannot cover every possible use case.

Suppose you're building a web app and want to raise an exception when a user tries to access a part of the site that they shouldn't have access to? None of Ruby's built-in exceptions really fit, so your best bet is to create a custom exception.

To create a custom exception, simply create a new class that inherits from `StandardError`.

```ruby
class PermissionDeniedError < StandardError

  def initialize(message)
    # Call the parent's(StandardError) constructor to set the message
    super(message)
  end

end

# Then, when the user tries to do something they don't
# have permission to do, you might do something like this:
raise PermissionDeniedError.new("Permission Denied!!!")
```

##The Exception Class Hierarchy
We made a custom exception by creating a subclass of `StandardError`, which itself is a subclass of `Exception`.
If you look at the class hierarchy, you'll see that everything subclasses `Exception`.

![](http://rubyblog.pro/images/posts/exceptions_hierarchy.jpg)

Don't worry, you don't need to memorize all of that. But it's important that you're familiar with class hierarchies in general for a very good reason.

**Rescuing errors of a specific class will also rescue errors of its subclasses.**

When you rescue StandardError, you not only rescue exceptions  of `StandardError` but those of its children as well. If you look at the chart, you'll see that's a lot: ArgumentError, NameError, TypeError, etc.

Therefore, if you were to rescue `Exception`, you would rescue every single exception, which is a **bad** idea. Don't do this!

Ruby uses exceptions for things other than errors, so rescuing *all* errors will cause your program to break in weird ways. Always rescue *specific* exceptions. When in doubt, use `StandardError`.

---

#Validation

Now that we have fair understanding of exceptions, let's look at **validations** and how it all ties in.

Many things that we do in our day to day lives can now be done online; whether it's shopping, banking, making dinner reservations, etc. But what's at the heart of all these processes? **Data**, it's all data. 

Therefore, to ensure that everything runs smoothly, data integrity needs to be ensured. This is accomplished by **validating** the data that's provided before it can be saved to a database.

As you know, Rails is structured on the [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) architecture. The **M**, which is the **m**odel layer, is concerned with managing how objects are created and stored in a database. In Rails, this layer is run by [Active Record](https://guides.rubyonrails.org/active_record_basics.html) by default, therefore, Active Record is concerned with handling the crucial task of data validation.

Validations are simply a set of rules that determine whether data is valid based on some criteria. Every model object contains an *errors* collection. In valid models, this collection contains no errors and is empty. When you declare validation rules on a certain model and it fails to pass, then an **exception** will be raised and that errors collection will contain errors consistent with the rules you've set, and this model will not be valid. A simple example is checking whether an online form has all the necessary data before it's submitted. If a field is left blank, an error message will be shown to the user so that they can fill it in.

##Simple validations

Let's look at two basic validation methods.

###Validating the presence and/or absence of attributes

This validation checks for the presence(or absence) of an attribute. In the example below, we're checking to see if the  `username` attribute is not only present, but also unique. This helps you avoid having duplicate usernames within your application. You check these with the `presence` and `uniqueness` [validation helpers](https://guides.rubyonrails.org/active_record_validations.html#validation-helpers) respectively, using the `validates` keyword, followed by the attribute name:

```ruby
class User < ApplicationRecord
   #validates presence and uniqueness of a username upon 
   #creation
   validates :username, presence: true, uniqueness: true
end
```
###Validating character length on an attribute
For a `password` attribute, we can check if the password that we're trying to set meets our specified requirements. In this case, we're making sure that it has a minimum length of 8 characters with the [length](https://guides.rubyonrails.org/active_record_validations.html#length) helper:

```ruby
class User < ApplicationRecord
  validates :username, presence: true, uniqueness: true
  #validates minimum length of 8 characters
  validates :password, length: { minimum: 8 }
end
```

##Working with Validation Errors

So how do you handle a validation error? As mentioned earlier, when a validation fails, an exception is raised and an `errors` collection is generated. 

Let's see what happens when we try to create a user account with a password that is less than 8 characters:


```ruby
class User < ApplicationRecord
  validates :username, presence: true, uniqueness: true
  validates :password, length: { minimum: 8 }
end

user = User.create(username: admin, password: "Welcome")

pp user

# => #<User:0x00007fd0cca748e8
# id: nil,
# username: "admin",
# password: "Welcome",
# created_at: nil,
# updated_at: nil> 
```
We get back what appears to be a `User` instance, but upon closer inspection we see that the `id` attribute is `nil`, meaning the `User` instance wasn't saved to the database. Good to know, but we need to see why exactly this record is invalid. To raise an exception and see more details about the error, we need to append a [bang](https://riptutorial.com/ruby-on-rails/example/9285/activerecord-bang-----methods)(!) to our `create` method:

```ruby
class User < ApplicationRecord
  validates :username, presence: true, uniqueness: true
  validates :password, length: { minimum: 8 }
end

user = User.create!(username: admin, password: "Welcome")

# => ActiveRecord::RecordInvalid (Validation failed: Password is # too short (minimum is 8 characters)) 
```
Here we get a `RecordInvalid` exception, one of many exception subclasses of `ActiveRecordError`, which itself is a subclass of `StandardError`. See the list of Active Record exceptions [here](https://github.com/rails/rails/blob/main/activerecord/lib/active_record/errors.rb).

`RecordInvalid` is raised when a record cannot be saved to the database due to a failed validation. In this case, our password was too short.

Remember I mentioned that an errors collection is also generated? Here's how to access it:

```ruby
class User < ApplicationRecord
  validates :username, presence: true, uniqueness: true
  validates :password, length: { minimum: 8 }
end

user = User.create!(username: nil, password: "Welcome")
# => ActiveRecord::RecordInvalid (Validation failed: Username can't be blank, Password is too short (minimum is 8 characters))

#Accesses an array of error messages from the `errors` object.
user.errors.full_messages
# => ["Username can't be blank", "Password is too short (minimum is 8 characters)"] 
```
Having an easily accessible array of validation errors is useful because we can communicate them to the frontend.

---

Going back to our `User` model, let's add a `create` [method](https://guides.rubyonrails.org/active_record_basics.html#crud-reading-and-writing-data) to create a new user account.

```ruby
def create
    #remember to append a "!" to your create method
    user = User.create!(user_params)
    session[:user_id] = user.id
    render json: user, status: :created
end

private

def user_params
    params.permit(:username, :password, 
    :password_confirmation)
end
```
Now in the event of a failed validation, in this case a blank username and/or a short password, a `RecordInvalid` exception will be raised. You can now add some conditional logic to display errors back to the frontend, let's update our `create` method:

```ruby
def create
  begin
    user = User.create!(user_params)
    session[:user_id] = user.id
    render json: user, status: :created
  rescue ActiveRecord::RecordInvalid => exception
    render json: {errors: 
    exception.record.errors.full_messages}, status: 
    :unprocessable_entity
  end
end
```
In our rescue block, we are passing in the exception as an argument and accessing our errors collection located at `exception.record.errors.full_messages`. Then we wrap it up in a nice JSON format so that it may be passed to the frontend, with a HTTP status code of `422` aka [Unprocessable Entity](http://www.railsstatuscodes.com/unprocessable_entity.html) using the `status:` method. You could also just type `status: 422`, but this is more readable.

###Isn't there an easier way?

You can see how this can get repetitive fast, especially if you're working with multiple models and controllers. You'd have to have the same `begin/rescue` block in all `create` actions throughout your controllers just to handle this one exception. Thankfully there's a way to abstract away some of this logic and make your life easier.

In Rails, you'll notice each controller inherits from `ApplicationController`, and thus inherits all of its methods. We can take advantage of this by moving the logic for handling a `RecordInvalid` exception up to `ApplicationController`, but we'll do it a little differently this time:

```ruby
class ApplicationController < ActionController::API
   #rescue_from takes an exception class, and an exception 
   #handler method
   rescue_from ActiveRecord::RecordInvalid, with: 
   :unprocessable_entity_response

   private

   def unprocessable_entity_response(exception)
     render json: {errors: 
     exception.record.errors.full_messages}, status: 
     :unprocessable_entity
   end
end
```

Instead of a begin/rescue block, we'll use the [rescue_from](https://edgeapi.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html) method, which takes an exception class, and an exception handler specified by a trailing `with:` option containing the name of an exception handler method. In this case we created a private class method called `unprocessable_entity_response`. So, when the specified exception is raised, `rescue_from` will pass the exception object to the exception handler specified in the `with:` option.

##On the frontend...

Displaying validation errors on the frontend is as simple as getting them through a fetch request and using conditional rendering to display them to a user when a validation error occurs.

This is an example from an account sign up form that I made previously using React:

```javascript
function SignupForm() {
    const [username, setUsername] = useState("")
    const [password, setPassword] = useState("")
    const [passwordConfirmation, setPasswordConfirmation] = 
    useState("")

   //Any validation errors that are returned will be stored 
   in state...
    const [errors, setErrors] = useState([])

    async function handleSubmit(e) {
        e.preventDefault();

        const res = await fetch("/api/signup", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                username,
                password,
                password_confirmation: passwordConfirmation
            })
        })

        if (res.ok) {
            const user = await res.json()
            setUser(user)
        } else {
            //if the fetch request fails and returns an error 
            object, the errors will be stored in state using 
            setErrors
            const err = await res.json()
            setErrors(err.errors)
        }

    }
```

In the return statement, you can use conditional rendering to display the errors if they are present in the `errors` array in state:

```javascript
return (
   {errors.map((err) => <Alert key={err} severity="error">{err}</Alert>)}
)
```

The result:

![](https://im3.ezgif.com/tmp/ezgif-3-c7006e2b64.gif)

---

#Resources

https://guides.rubyonrails.org/active_record_validations.html
http://www.railsstatuscodes.com/
https://www.rubydoc.info/docs/rails/4.1.7/ActiveRecord/ActiveRecordError
https://ruby-doc.org/core-2.5.1/Exception.html