Exception handling is a crucial part of any programming language, and Ruby is no exception. It allows you to manage errors gracefully and ensure that your program can handle unexpected situations without crashing.
Key Concepts
- Exceptions: An exception is an event that disrupts the normal flow of a program. In Ruby, exceptions are objects that are instances of the
Exception
class or its subclasses. - Raising Exceptions: You can raise exceptions using the
raise
keyword. - Rescuing Exceptions: You can handle exceptions using the
begin-rescue-end
block. - Ensuring Execution: The
ensure
block allows you to run code regardless of whether an exception was raised or not. - Custom Exceptions: You can define your own exception classes by inheriting from the
StandardError
class.
Raising Exceptions
You can raise an exception using the raise
keyword. Here’s a simple example:
def divide(a, b) raise "Division by zero error" if b == 0 a / b end begin puts divide(10, 0) rescue => e puts "An error occurred: #{e.message}" end
Explanation
raise "Division by zero error"
: Raises an exception with the message "Division by zero error" ifb
is zero.begin-rescue-end
block: This block is used to handle exceptions. If an exception is raised within thebegin
block, therescue
block will catch it.rescue => e
: Captures the exception object in the variablee
.e.message
: Retrieves the error message from the exception object.
Rescuing Specific Exceptions
You can rescue specific exceptions by specifying the exception class:
begin File.open("non_existent_file.txt") rescue Errno::ENOENT => e puts "File not found: #{e.message}" end
Explanation
rescue Errno::ENOENT => e
: This line rescues onlyErrno::ENOENT
exceptions, which are raised when a file is not found.
Ensuring Execution
The ensure
block runs code regardless of whether an exception was raised or not:
begin file = File.open("example.txt", "w") file.write("Hello, world!") rescue => e puts "An error occurred: #{e.message}" ensure file.close if file end
Explanation
ensure
block: Ensures that the file is closed whether an exception occurs or not.
Custom Exceptions
You can create custom exceptions by subclassing StandardError
:
class CustomError < StandardError; end def risky_method raise CustomError, "Something went wrong!" end begin risky_method rescue CustomError => e puts "Caught a custom error: #{e.message}" end
Explanation
class CustomError < StandardError; end
: Defines a custom exception class.raise CustomError, "Something went wrong!"
: Raises an instance of the custom exception.rescue CustomError => e
: Catches the custom exception.
Practical Exercises
Exercise 1: Basic Exception Handling
Write a method safe_divide
that takes two arguments and returns their division. If the second argument is zero, it should raise an exception with the message "Cannot divide by zero".
def safe_divide(a, b) # Your code here end begin puts safe_divide(10, 2) # Should print 5 puts safe_divide(10, 0) # Should raise an exception rescue => e puts "Error: #{e.message}" end
Solution
def safe_divide(a, b) raise "Cannot divide by zero" if b == 0 a / b end begin puts safe_divide(10, 2) # Should print 5 puts safe_divide(10, 0) # Should raise an exception rescue => e puts "Error: #{e.message}" end
Exercise 2: Custom Exception
Create a custom exception NegativeNumberError
and write a method square_root
that takes a number and returns its square root. If the number is negative, it should raise the custom exception.
class NegativeNumberError < StandardError; end def square_root(number) # Your code here end begin puts square_root(9) # Should print 3.0 puts square_root(-1) # Should raise NegativeNumberError rescue NegativeNumberError => e puts "Error: #{e.message}" end
Solution
class NegativeNumberError < StandardError; end def square_root(number) raise NegativeNumberError, "Cannot take the square root of a negative number" if number < 0 Math.sqrt(number) end begin puts square_root(9) # Should print 3.0 puts square_root(-1) # Should raise NegativeNumberError rescue NegativeNumberError => e puts "Error: #{e.message}" end
Common Mistakes and Tips
- Not rescuing specific exceptions: Always try to rescue specific exceptions rather than using a generic
rescue
clause. This makes your code more robust and easier to debug. - Forgetting the
ensure
block: Use theensure
block to release resources like file handles or database connections. - Raising exceptions unnecessarily: Only raise exceptions for truly exceptional conditions. Overusing exceptions can make your code harder to read and maintain.
Conclusion
In this section, you learned how to handle exceptions in Ruby using the raise
, rescue
, and ensure
keywords. You also learned how to create custom exceptions to handle specific error conditions. Exception handling is a powerful tool that helps you write robust and error-resistant code. In the next section, we will dive into file input/output operations in Ruby.
Ruby Programming Course
Module 1: Introduction to Ruby
Module 2: Basic Ruby Concepts
Module 3: Working with Collections
Module 4: Object-Oriented Programming in Ruby
- Classes and Objects
- Instance Variables and Methods
- Class Variables and Methods
- Inheritance
- Modules and Mixins
Module 5: Advanced Ruby Concepts
Module 6: Ruby on Rails Introduction
- What is Ruby on Rails?
- Setting Up Rails Environment
- Creating a Simple Rails Application
- MVC Architecture
- Routing
Module 7: Testing in Ruby
- Introduction to Testing
- Unit Testing with Minitest
- Behavior-Driven Development with RSpec
- Mocking and Stubbing