Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
April 28, 2023 03:00 pm GMT

Exception Handling and Validations in Rails, and how to display errors to users.

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:

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:

#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!"endbegin  # Any exceptions in here...rescue  # ...will trigger this block of code  handle_exceptionend# => "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). 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 exception class.

#Rescues all errors, and assigns the exception object to an `error` variablerescue => error#Rescues only ZeroDivisionError and assigns the exception object to an `error` variablerescue 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.

begin  do_somethingrescue => error  # This is the same as rescuing StandardError  # and all of its subclassesend

Now.. let's go back and take a closer look at the exception object for ZeroDivisionError

begin  # Any exceptions in here...  1 / 0rescue 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.

begin  #raises an ArgumentError with the message "You screwed up!"  raise ArgumentError.new("You screwed up!")rescue ArgumentError => error  puts error.messageend# => "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.

class PermissionDeniedError < StandardError  def initialize(message)    # Call the parent's(StandardError) constructor to set the message    super(message)  endend# 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.

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 architecture. The M, which is the model layer, is concerned with managing how objects are created and stored in a database. In Rails, this layer is run by Active Record 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 respectively, using the validates keyword, followed by the attribute name:

class User < ApplicationRecord   #validates presence and uniqueness of a username upon    #creation   validates :username, presence: true, uniqueness: trueend

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 helper:

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:

class User < ApplicationRecord  validates :username, presence: true, uniqueness: true  validates :password, length: { minimum: 8 }enduser = 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(!) to our create method:

class User < ApplicationRecord  validates :username, presence: true, uniqueness: true  validates :password, length: { minimum: 8 }enduser = 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.

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:

class User < ApplicationRecord  validates :username, presence: true, uniqueness: true  validates :password, length: { minimum: 8 }enduser = 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 to create a new user account.

def create    #remember to append a "!" to your create method    user = User.create!(user_params)    session[:user_id] = user.id    render json: user, status: :createdendprivatedef 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:

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  endend

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 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:

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   endend

Instead of a begin/rescue block, we'll use the rescue_from 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:

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:

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

The result:

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


Original Link: https://dev.to/jaguilar89/exception-handling-and-validations-in-rails-and-how-to-display-errors-to-users-505l

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To