What are exceptions & Why They Happen?
We use exceptions to handle unexpected failures inside a program. Exceptions are in the form of a program crash with an error message.
In Ruby, Exceptions are implemented as classes inherited from the Exception class used to signal an error condition when the program cannot finish its work in the current state.
You might have seen exceptions a few times before, an example related to ActiveRecord
ActiveRecord::RecordNotFound- Couldn't find Article with 'id'=12
activerecord (4.2.0) lib/active_record/core.rb:154:in 'find'
app/controllers/articles_controller.rb:77:in 'set_article'
activesupport (4.2.0) lib/active_support/callbacks.rb:427:in
This error message is composed of:
- the exception class (ActiveRecord::RecordNotFound)
- the exception message ("Couldn't find Article with id= 12)
- the stack trace
These are important pieces of information to help us understand and fix the problem.
An exception does not always mean that your program has to end. For Example:
File.open("random_file.txt")
Assuming that random_file.txt doesn't exist, we will get an error:
open-file.rb:1:in 'initialize': File not found No such file or directory @ rb_sysopen - random_file.txt (Errno::ENOENT)
The exception, in this case, is Errno::ENOENT
The Errno family of exceptions are all related to the operating system.
We may not want this code to crash in the larger program, so we use the begin/rescue
block. In other programming languages, there will be a try/catch/throw the block for exception handling
For Example:
begin
File.open("random_file.txt")
rescue Errno::ENOENT => e
puts "File not found"
puts e.message
puts e.backtrace
end
This code will print the "File not found" with the Ruby Exception of Errno::ENOENT If we use rescue without an exception name then StandardError is the default trapped exception. This will also trap all the exceptions that inherit from StandardError.
Example without begin
def open_file
File.open("random_file.txt")
rescue Errno::ENOENT => e
puts "File not found"
puts e.message
puts e.backtrace
end
Another keyword related to exceptions that we can use is ensured.
The code ensured will always run, even if an exception happens.
For Example:
begin
puts "test example"
ensure
puts "Always print this test example"
end
Another example:
def call
@article.lock
begin
puts "do something"
ensure
@article.unlock
end
end
There is one more keyword: retry. When we use retry inside a rescue block, it will re-run the beginning block. I would recommend that we avoid using retry because we can easily get into infinite loops and other issues.
Exceptions and Return Values
The last thing evaluated in a method is what gets returned from the method which is also called implicit return The rescue part of our code doesn't get evaluated unless there is an exception. For Example:
def example_exception(exception = false)
raise if exception
1
rescue
"print no exception"
end
end
p example_exception
# => 1
p example_exception(true)
# => "print no exception"
When a ruby program ends for any reason or if we need to end the program immediately without going through any kind of callbacks we can also use at_exit or exit! method This can be useful to write some crash logger.
at_exit do
puts "Program finished at #{Time.now}"
end
We can also create our own custom exception classes, as Ruby has several built-in exception classes.
Example
class InvalidArticle < StandardError
end
raise InvalidArticle, "the article posted is invalid"
There are a list of built-in exceptions, you can check it out on this link