Ruby error message

The other day I was searching for an introduction to Ruby exceptions written for beginners - people who know basic Ruby syntax but aren't really sure what an exception is or why it's useful. I couldn't find one, so I decided to have a go at it myself. I hope you find it useful.

The other day I was searching for an introduction to Ruby exceptions written for beginners — people who know basic Ruby syntax but aren’t really sure what an exception is or why it’s useful. I couldn’t find one, so I decided to have a go at it myself. I hope you find it useful. If there are any points that are confusing, feel free to tweet at me at @StarrHorne. :)

What’s an exception?

Exceptions are Ruby’s way of dealing with unexpected events.

If you’ve ever made a typo in your code, causing your program to crash with a message like SyntaxError or NoMethodError, then you’ve seen exceptions in action.

When you raise an exception in Ruby, the world stops and your program starts to shut down. If nothing stops the process, your program will eventually exit with an error message.

Here’s an example. In the code below, we try to divide by zero. This is impossible, so Ruby raises an exception called ZeroDivisionError. The program quits and prints an error message.

1 / 0
# Program crashes and outputs: "ZeroDivisionError: divided by 0"

Crashing programs tend to make our users angry. So we normally want to stop this shutdown process, and react to the error intelligently.

This is called «rescuing,» «handling,» or «catching» an exception. They all mean the same thing. This is how you do it in Ruby:

begin
  # Any exceptions in here... 
  1/0
rescue
  # ...will cause this code to run
  puts "Got an exception, but I'm responding intelligently!"
  do_something_intelligent()
end

# This program does not crash.
# Outputs: "Got an exception, but I'm responding intelligently!"

The exception still happens, but it doesn’t cause the program to crash because it was «rescued.» Instead of exiting, Ruby runs the code in the rescue block, which prints out a message.

This is nice, but it has one big limitation. It tells us «something went wrong,» without letting us know what went wrong.

All of the information about what went wrong is going to be contained in an exception object.

Exception Objects

Exception objects are normal Ruby objects. They hold all of the data about «what happened» for the exception you just rescued.

To get the exception object, you will use a slightly different rescue syntax.

# Rescues all errors, an puts the exception object in `e`
rescue => e

# Rescues only ZeroDivisionError and puts the exception object in `e`
rescue ZeroDivisionError => e

In the second example above, ZeroDivisionError is the class of the object in e. All of the «types» of exceptions we’ve talked about are really just class names.

Exception objects also hold useful debug data. Let’s take a look at the exception object for our ZeroDivisionError.

begin
  # Any exceptions in here... 
  1/0
rescue ZeroDivisionError => e
  puts "Exception Class: #{ e.class.name }"
  puts "Exception Message: #{ e.message }"
  puts "Exception Backtrace: #{ e.backtrace }"
end

# Outputs:
# Exception Class: ZeroDivisionError
# Exception Message: divided by 0
# Exception Backtrace: ...backtrace as an array...

Like most Ruby exceptions, it contains a message and a backtrace along with its class name.

Raising Your Own Exceptions

So far we’ve only talked about rescuing exceptions. You can also trigger your own exceptions. This process is called «raising.» You do it by calling the raise method.

When you raise your own exceptions, you get to pick which type of exception to use. You also get to set the error message.

Here’s an example:

begin
  # raises an ArgumentError with the message "you messed up!"
  raise ArgumentError.new("You messed up!")
rescue ArgumentError => e  
  puts e.message
end

# Outputs: You messed up! 

As you can see, we’re creating a new error object (an ArgumentError) with a custom message («You messed up!») and passing it to the raise method.

This being Ruby, raise can be called in several ways:

# This is my favorite because it's so explicit
raise RuntimeError.new("You messed up!")

# ...produces the same result
raise RuntimeError, "You messed up!"

# ...produces the same result. But you can only raise 
# RuntimeErrors this way
raise "You messed up!"

Making Custom Exceptions

Ruby’s built-in exceptions are great, but they don’t cover every possible use case.

What if you’re building a user system and want to raise an exception when the user tries to access an off-limits part of the site? None of Ruby’s standard exceptions fit, so your best bet is to create a new kind of exception.

To make a custom exception, just create a new class that inherits from StandardError.

class PermissionDeniedError < StandardError

end

raise PermissionDeniedError.new()

This is just a normal Ruby class. That means you can add methods and data to it like any other class. Let’s add an attribute called «action»:

class PermissionDeniedError < StandardError

  attr_reader :action

  def initialize(message, action)
    # Call the parent's constructor to set the message
    super(message)

    # Store the action in an instance variable
    @action = action
  end

end

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

The Class Hierarchy

We just made a custom exception by subclassing StandardError, which itself subclasses Exception.

In fact, if you look at the class hierarchy of any exception in Ruby, you’ll find it eventually leads back to Exception. Here, I’ll prove it to you. These are most of Ruby’s built-in exceptions, displayed hierarchically:

Exception
 NoMemoryError
 ScriptError
   LoadError
   NotImplementedError
   SyntaxError
 SignalException
   Interrupt
 StandardError
   ArgumentError
   IOError
     EOFError
   IndexError
   LocalJumpError
   NameError
     NoMethodError
   RangeError
     FloatDomainError
   RegexpError
   RuntimeError
   SecurityError
   SystemCallError
   SystemStackError
   ThreadError
   TypeError
   ZeroDivisionError
 SystemExit

You don’t have to memorize all of these, of course. I’m showing them to you because this idea of hierarchy is very important for a specific reason.

Rescuing errors of a specific class also rescues errors of its child classes.

Let’s try that again…

When you rescue StandardError, you not only rescue exceptions with class StandardError but those of its children as well. If you look at the chart, you’ll see that’s a lot: ArgumentError, IOError, etc.

If you were to rescue Exception, you would rescue every single exception, which would be a very bad idea

Rescuing All Exceptions (the bad way)

If you want to get yelled at, go to stack overflow and post some code that looks like this:

// Don't do this 
begin
  do_something()
rescue Exception => e
  ...
end

The code above will rescue every exception. Don’t do it! It’ll break your program in weird ways.

That’s because Ruby uses exceptions for things other than errors. It also uses them to handle messages from the operating system called «Signals.» If you’ve ever pressed «ctrl-c» to exit a program, you’ve used a signal. By suppressing all exceptions, you also suppress those signals.

There are also some kinds of exceptions — like syntax errors — that really should cause your program to crash. If you suppress them, you’ll never know when you make typos or other mistakes.

Rescuing All Errors (The Right Way)

Go back and look at the class hierarchy chart and you’ll see that all of the errors you’d want to rescue are children of StandardError

That means that if you want to rescue «all errors» you should rescue StandardError.

begin
  do_something()
rescue StandardError => e
  # Only your app's exceptions are swallowed. Things like SyntaxErrror are left alone. 
end

In fact, if you don’t specify an exception class, Ruby assumes you mean StandardError

begin
  do_something()
rescue => e
  # This is the same as rescuing StandardError
end

Rescuing Specific Errors (The Best Approach)

Now that you know how to rescue all errors, you should know that it’s usually a bad idea, a code smell, considered harmful, etc.

They’re usually a sign that you were too lazy to figure out which specific exceptions needed to be rescued. And they will almost always come back to haunt you.

So take the time and do it right. Rescue specific exceptions.

begin
  do_something()
rescue Errno::ETIMEDOUT => e
  // This will only rescue Errno::ETIMEDOUT exceptions
end

You can even rescue multiple kinds of exceptions in the same rescue block, so no excuses. :)

begin
  do_something()
rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
end

So you’ve decided to learn how to code. The first thing you need to know about this road you’ve started down is that programming is awesome. Let me repeat that, just to make sure you heard me. Programming is really, really awesome. You may have already built a program that outputs “Hello, World!” to your terminal. If so, you’ve experienced feelings of joy at seeing a program you wrote yourself work properly. Know that that feeling pales in comparison to the feeling you’ll have when you build your first complex application from empty file to finished product. If your experience is like mine, that will be the moment you fall hopelessly in love with code.

But before I let my romanticism lead me off topic, there’s something else you should know about programming. It’s hard. Let me repeat that, just to make sure you heard me. Programming is really, really hard. One of the hardest things about it, I think, especially for the total beginner, is errors. You’ll see error messages way more often than you’ll see your code actually working, so learning to read and understand them is an essential skill that you have to develop. I struggled to understand error messages for the first several months of my programming journey. But when someone finally taught me how to read them, it completely changed my world. Debugging was so much easier (and more fun), and I started to become less frustrated. So I’m writing this for you, dear code newbie, because I wish someone had written something like this for me when I started. We’re going to talk about what errors are, the five most common errors you’ll encounter, what the errors mean, and how to resolve them. I won’t be able to tell you how to deal with every situation in which you might see these errors, but I’ll try and give you a basic framework to work with.

Quick aside: please bear with me if my examples seem a bit contrived. I’m using simple scenarios to explain error messages, even though the code might be unrealistic.

WHAT ARE ERRORS?

Ruby will throw, or raise, an error when something in your program is not working properly. Error messages are your interpreter telling you that something needs to be fixed. If you don’t know what I mean by «interpreter», don’t worry so much about it now. It’s beyond the scope of this post, but just understand that it has to do with how your code is read and executed in your computer. (If you’re dying to know more, Google around, but finish reading this first).

Write this down on a Post-it and stick it next to your computer: errors are not setbacks, they’re opportunities. When your program throws an error, it doesn’t mean that you’re a bad programmer. I think that many of us in the beginning stages feel that if we were better at programming we could build an application and see minimal errors. Thinking this way will only lead to misery, so the first thing you need to realize is that errors are normal. Your program won’t be transmitted from your brain to your text editor in flawless form with awe-inspiring functionality. It will start broken, and require you to fix it. This is the fun part about programming, believe it or not. This is where you get to solve problems and gain the satisfaction that comes from that. And that’s what errors are there for. Errors are your friend, not your foe. Listen to them. They want to help you. Frustration is normal, but always remember that running into errors does not mean you’re a bad programmer. Instead, read on, and learn how to use them to your advantage.

HOW DO I READ AN ERROR MESSAGE?

Error messages give you three key pieces of information, the “where”, the “why”, and the “what”.

A typical error message looks like this:

  
lib/my_file.rb:32::undefined local variable or method `some_variable' for main:Object (NameError)
  

lib/my_file.rb:32 tells you that the error occurred in the lib directory, in the file my_file.rb, on line 32 of that file. That’s the «where».

undefined local variable or method `some_variable' tells you what went wrong. In this case, that Ruby found a local variable or method it didn’t recognize, and it needs to be defined. That’s the «why».

(NameError) tells you the type of exception Ruby raised. In this case, a NameError. You got it, that’s the «what».

The most important piece of advice I can give you is to always read the whole error. When you’re debugging, you’re a detective and you need all the clues you can find. First, find out where the error occurred, and go to that line. Read that line of code, and all the lines immediately preceding and following it. Are there any other methods that call the one where the error occurred? Read those too. Once you do that, you can read the rest of the error message, and find out why the error was thrown, and what error it is. Suddenly you are no longer blindly trying to debug your code, but you have some perspective. You have information that you can use to figure out exactly what went wrong.

Let’s start with this one, and talk about a few other common errors you’ll encounter.

NameError

What the what?

Generally, this error is raised when you call a variable or method that Ruby doesn’t recognize.

How do I fix it?

The first question to ask yourself is, did you define the method or variable in question? I can’t tell you how many times I’ve tried to call a variable that I forgot to define. Check your spelling. Sometimes the problem is as simple as a spelling error in the variable or method you’re trying to call. Or did you assign something to a variable called «user_input», but mistakenly call it «input» in your method? Have you actually built the method you’re trying to call? If that all checks out, then you need to think about scope (if you have no idea what I mean by scope, click here). Did you assign a local variable in one method and try to call it in another?

SyntaxError

 
32: syntax error, unexpected end-of-input, expecting keyword_end
 

What the what?

By far the most common error I encounter every day is the syntax error. You’ll see this bad boy pop up when there is something amiss with the structure of your code. For instance, if you forget an end somewhere.

How do I fix it?

Do what the error says. Fixing this one is as simple as adding an end where it says it needs one. Except when it’s not. Sometimes the error message will say you need a closing end but the source of your trouble is actually several lines above. Start at the top of your code and follow it down, making sure that everything is syntactically correct. Did you iterate over something (do) and forget to close it with an end? Are all your if, elsif, and else statements being used properly? One thing that will help you immensely is to learn to indent your code properly as you write it. Keep your code as organized and clean as you can. It will make it much easier to catch your syntax mistakes along the way.

NoMethodError

What the what?

Closely related to the NameError is the NoMethodError. This means that you are trying to call a method on an object that doesn’t know how to do what you’re asking it to do. If you try and do math with a number that’s a string and not an integer, for instance, or if you created a variable with a nil value, and then forgot to assign another value to it.

Imagine a scenario in which you want to manipulate a range of numbers. You’re not sure how to begin, so you start with something like this:

 
def split_numbers 
  1..10.split
end
 

If you try running this code to see what it returns, you would see:

 
errors.rb:2:in `split_numbers': undefined method `split' for 10:Integer (NoMethodError)
 

How do I fix it?

Google around and make sure you’re using the right method for your object. In the example above, calling .split won’t work because you can’t call it on an integer, even if you’re dealing with a range of integers. It’s a method that operates on strings. Take a close look at your code, as well. It might not be so much that you’re using the wrong method, but maybe you’re using it in the wrong way.

ArgumentError

What the what?

Say we have the following method:

 
def upcase_this_string(string)
  puts string.upcase
end
 

If we call upcase_this_string("I love Ruby"), it will output «I LOVE RUBY». But if we call upcase_this_string("I love Ruby", "a lot"), we’ll see this:

 
1:in `upcase_this_string': wrong number of arguments (given 2, expected 1) (ArgumentError)
 

What this is telling you is that you gave two arguments in your method call, but your method was written to only take one argument.

How do I fix it?

There are a few scenarios that will cause Ruby to throw an ArgumentError, so make sure the number of arguments are correct, and that the datatype you are trying to use is valid.

TypeError

What the what?

This particular error had me confused for a long time, and I’m sure I’m not alone, so let’s dig in. Let’s say you’re doing some super simple arithmetic:

 
def add_one_plus_one
  puts 1+1
end
 

Calling add_one_plus_one and running this file will output 2 to your terminal. but what if instead of puts 1+1, you had for some reason written puts "one"+1? You would see:

 
errors.rb:2:in `+': no implicit conversion of Integer into String (TypeError)
 

How do I fix it?

Basically, all Ruby is telling you here is that when you tried to call +1, it expected the value behind the + sign to be an integer as well. Instead, you tried to call +1 on a string. Ruby encountered an object that wasn’t the type of object it expected it to be. So make sure that for whatever operation you are trying to perform, you are using the proper type of object. For this example, it would have to be either 1+1 which would output 2, or "one"+"one", which would result in "oneone".

FUN-FACT

You’ve probably heard that Ruby is a pure object-oriented language, and that everything in Ruby is an object. Would it blow your mind if I pointed out that this holds true for your errors as well? Errors are objects, instances of the Exception class. If you don’t find this cool yet, just wait. Object-orientation is awesome. Suspend disbelief until you get there, and you’ll see what I mean.

RESOURCES

For more information on the Exception class, and Ruby errors, see the Ruby docs

If I’ve done my job, error messages now seem more understandable, less intimidating, and you now have some idea how to resolve them. If you found this post helpful, please share it with anybody else who might benefit from it. I am always eager to learn and improve, so I would love to hear what people think of this.
If you feel so inclined, follow me on Twitter. I always love to meet new people in the programming community.

Happy coding!

Исключения

Исключение — это допустимая ошибка, возникающая в процессе выполнения программы, прерывающая выполнение до тех пор, пока не ислючение не будет обработано. Если исключение не обрабатывается, то выполнение программы прекращается.

Каждое исключение относится к классу Exception и его производным. Обычно название каждого подкласса, содержит полную информацию о причине возникновения исключения.

Для создания новых типов исключений обычно используются классы StandartError и RuntimeError.

Системные ошибки, имеющие стандартный цифровой код, также относятся к исключениям. Модуль Errno динамически связывает полученные от операционной системы цифровые коды с подклассами Exception. При этом для каждой ошибки создается собственный подкласс SystemCallError, на который ссылается константа в модуле Errno. Цифровой код ошибки может быть получен с помощью константы Errno (Errno::<ErrorKlass>::Errno).

Иерархия исключений

  • Exception — базовый класс для всех исключений.

    • NoMemoryError — выделение памяти не может быть выполнено;

      ScriptError— базовый класс для ошибок интерпретации;
      + LoadError — файл не может быть загружен;
      NotImplemenetedError — метод не поддерживается системой;
      SyntaxError — ошибка в синтаксисе;

      SecuirityError — нарушение требований безопасности;

      SignalException — получение сигнала от системы;
      + Interrupt — сигнал прервать процесс выполнения (обычно Ctrl+C);

      SystemExit — завершение выполнения программы системой;

      SystemStackError — переполнение стека;

      StandardError — базовый класс для стандартных ошибок выполнения;
      + Math::DomainError — объекты не принадлежат области определения функции;
      ArgumentError — ошибка при передаче аргументов;

        _EncodingError_ - базовый класс для ошибок, связанных с кодировкой;
          + _Encoding::CompatibilityError_ - исходная кодировка не совместима с требуемой;  
            _Encoding::ConverterNotFoundError_ - требуемая кодировка не поддерживается;  
            _Encoding::InvalidByteSequenceError_ - текст содержит некорректные байты;  
            _Encoding::UndefinedConversionError_ - текст содержит неопределенные символы;
      
        _FiberError_ - ошибка при работе с управляемыми блоками;
      
        _IOError_ - возникновение ошибки при работе с потоками;
          + _EOFError_ - достигнут конец файла;
      
        _IndexError_ - индекс не найден;
          + _KeyError_ - ключ не найден;  
            _StopIteration_ - завершение итерации;
      
        _LocalJumpError_ - блок не может быть выполнен;
      
        _NameError_ - неизвестный идентификатор;
          + _NoMethodError_ - неизвестный метод;
      
        _RangeError_ - выход за границы диапазона;
          + _FloatDomainError_ - попытка преобразования констант для определения специальных чисел (NaN и т.д.);
      
        _RegexpError_ - ошибка в регулярном выражении;
      
        _RuntimeError_ - универсальный класс для ошибок выполнения;
      
        _SystemCallError_ - базовый класс для системных ошибок;
      
        _ThreadError_ - ошибка при работе с процессами;
      
        _TypeError_ - неправильный тип объекта. Данное исключение также возникает при объявлении наследования для существующего класса;
      
        _ZeroDivisionError_ - деление целого числа на ноль.
      

Методы

Exception

::exception( message = nil ) # -> exception

Используется для создания объекта. Для аргумента вызывается метод object.to_str.

::new( mesage = nil ) # -> exception

Используется для создания объекта.

.exception( message = nil ) # -> exception

Используется для получения нового экземпляра того же класс. Для аргумента вызывается метод .to_str.

.backtrace # -> array

Используется для получения данных о распространении исключения. Каждый элемент имеет вид:
"имя_файла:номер_строки: in 'идентификатор_метода'"
или
"имя_файла:номер_строки"

.set_backtrace(array) # -> array

Используется для изменения данных о распространении исключения.

.to_s # -> string
Синонимы: message

Сообщение об ошибке (или идентификатор класса).

.inspect # -> string Идентификатор класса.

LoadError [ruby 2.0]

.path # -> string

Метод используется для получения пути, по которому не был найден файл.

SignalException

::new(sig_name) # -> a_signal_exception

(sig_number, name = nil)

Метод используется для создания нового объекта. Название сигнала должно быть известно интерпретатору.

.signo # -> number

Метод используется для получения номера сигнала.

SystemExit

::new( status = 0 ) # -> exception

Используется для создания нового объекта.

.status # -> integer Статус завершения программы.

.success? # -> bool

Проверка удалось ли завершение программы.

Encoding::InvalidByteSequenceError

.destination_encoding # -> encoding Требуемая кодировка

.destination_encoding_name # -> string Название требуемой кодировки.

.source_encoding # -> encoding

Исходная кодировка. При нескольких преобразованиях исходной будет считаться последняя стабильная кодировка.

.source_encoding_name # -> string

Название исходной кодировки. При нескольких преобразованиях исходной будет считаться последняя стабильная кодировка.

.error_bytes # -> string

Байт из-за которого возникло исключение.

.incomplete_input? # -> bool

Проверка возникновения исключения из-за преждевременного завершения текста.

.readagain_bytes # -> string

Байт, обрабатываемый в момент возникновения исключения.

Encoding::UndefinedConversionError

.destination_encoding # -> encoding Требуемая кодировка

.destination_encoding_name # -> string Название требуемой кодировки.

.source_encoding # -> encoding

Исходная кодировка. При нескольких преобразованиях исходной будет считаться последняя стабильная кодировка.

.source_encoding_name # -> string

Название исходной кодировки. При нескольких преобразованиях исходной будет считаться последняя стабильная кодировка.

.error_char # -> string

Символ из-за которого возникла ошибка.

StopIteration

.result # -> object
Результат итерации.

LocalJumpError

.exit_value # -> object

Аргумент, передача которого привела к возникновению исключения.

.reason # -> symbol

Идентификатор инструкции, выполнение которой привело к возникновению исключения (:break, :redo, :retry, :next, :return, или :noreason).

NameError

::new( message, name = nil ) # -> exception

Используется для создания нового объекта.

.name # -> name

Идентификатор, использование которого привело к возникновению исключения.

NoMethodError

::new( message, name, *args ) # -> exception

Используется для создания нового объекта.

.args # -> object

Аргументы, переданные отсутствующему методу.

SystemCallError

::new( message, integer ) # -> exception

Используется для создания нового экземпляра класса из модуля Errno (если методу передан известный системе цифровой код ошибки) или класса SystemCallError.

.errno # -> integer Цифровой код ошибки.

Возникновение и обработка исключений

Вызов исключения

Вызов исключения выполняется с помощью частного метода экземпляров из модуля Kernel.

.raise( message = nil ) # -> exception

( exc = RuntimeError, message = nil, pos = caller ) # -> exception
Синонимы: fail

Используется для повторного вызова последнего исключения или создания нового ( RuntimeError), если $! ссылается на nil.

В другом случае методу передаются любой объект, отвечающий на вызов метода .exception, сообщение об ошибке и текущая позиция выполнения программы.

Обработка исключений

Обработка событий выполняется с помощью предложения rescue, которое может использоваться только в теле предложений begin, def, class, или module.

Исключения обрабатываются в том же порядке, в котором объявляются обработчики. При возникновении исключения интерпретатор останавливает процесс выполнения программы и начинает поиск обработчика, продвигаясь вверх по области вызова.

Если исключения возникло в результате обработки другого исключения, то поиск обработчиков выполняется заново.

После обработки исключения выполнение программы не продолжается.

Полный синтаксис

  begin
    тело_предложения
  rescue
    тело_обработчика
  else
    code
  ensure
    code
  end
  • Тело обработчика выполняется после возникновения исключения в теле предложения. Переменная $! при этом ссылается на конкретный экземпляр исключения.

    Чтобы инициализировать локальную переменную используют инструкцию
    rescue => локальная_переменная.

  • По умолчанию обрабатываются экземпляры StandardError и его производных.

    Для ограничения типов обрабатываемых исключений используют инструкцию rescue class или rescue class => локальная_переменная. Несколько классов разделяются запятыми.

  • Инструкция else выполняется если исключений не получено. При этом исключения, возникшие в теле инструкции не обрабатываются.

  • Инструкция ensure выполняется после выполнения всего предложения. Результат ее выполнения не влияет на результат выполнения предложения (кроме случаев использования инструкций return, break и т.д)

Краткий синтаксис:

код rescue тело_обработчика

Если в коде будет вызвана ошибка, то выполняется тело обработчика. Обрабатываются только экземпляры StandardError и его производных.

Catch и Throw

В других языках программирования обработка событий обычно выполняется с помощью пары инструкций catch и throw. В Ruby существуют частные методы экземпляров из модуля Kernel, ведущие себя сходным образом.

.catch(name = nil) { |name| } # -> object

Используется для создания прерываемого фрагмента кода. Выполнение останавливается при вызове метода object.throw с тем же идентификатором. При вызове без аргументов новый случайный идентификатор передается блоку.

.throw( name, *args )

Используется для завершения выполнения блока, переданного методу object.catch с тем же идентификатором (иначе возникает исключение). Поиск блока выполняетcя вверх по иерархии области видимости. Дополнительные аргументы возвращаются методом object.catch.

 on
November 19, 2019

What is exception handling?

Software systems can be quite prone to error conditions. Systems that involve user interaction are more vulnerable to exceptions as they attract errors at multiple fronts. Errors can take many forms — syntactical errors, network errors, form input errors, invalid authentication errors etc. If not accounted for, these can affect user experience and can even manifest as security loopholes, enabling attackers to compromise the system.

Exception handling is used to prevent system failures and to save the user from lousy error messages.

In most cases, when an error condition occurs, the programming language forces the program to terminate and therefore program control is lost. Exception handling allows you (or the program) to not lose control and to be able to deal with what happens after the error has occurred. This gives you some room to make amends — to show affable error messages, to release some resources or to conveniently redirect the user to a relevant page. 

Exception handling helps you clear up the mess, without making it look like one.

It is the process of accommodating error conditions and responding to them programmatically, resulting in execution of a presumably alternate, but already planned sequence of code.

Exception handling in Ruby

In the vast majority of languages, exception handling is performed using “try, throw and catch” mechanisms. When the try block is encountered, the compiler becomes super aware in the lookout for an exception (thrown either manually, or by the language itself) and instructs the catch block to deal with it. Ruby, however decided to go with different names for their conventional exception handling system. 

I find the Ruby terminology for dealing with exceptions a lot more poetic. 

There is a beginning, followed by errors raised and eventually rescued. And like all good things, it comes to an end, unless you want to retry.

begin
    # raise exception here

rescue
    # raised exception handled here

retry
    # retrying the code in the begin block


ensure
    # Always executed

end

Exception handling in Ruby primarily consists of —

  • Begin — end block
  • Raise
  • Rescue
  • Retry
  • Ensure

Begin — end block, raise, rescue and ensure are analogous to the programming concepts of try, throw, catch and finally. Let’s try to understand them.

Note: Ruby also provides throw and catch functions for handling exceptions, which are lightweight counterparts of raise and rescue that do not build stack traces of the exceptions. Even though raise and rescue based mechanisms are more prevalent among Ruby developers, we’ll see how Ruby’s throw and catch mechanisms can help later in this post.

2.1 Begin — end block

The error prone part of your code comes in the begin block. The corresponding exception handling, through the raise and rescue mechanisms, also happens inside the begin block.

You can use begin blocks to sectionalize the different types of errors that are likely to show up. Differently erroneous codes can be put into different begin — end blocks and can accordingly be handled.

begin
    # code likely to give error comes in this block
    # raising and rescuing something
end

begin
    # raising and rescuing something else
end

Note: The body of a Ruby method can also act as begin-end block and thus does not require an explicit ‘begin’ call.

def raise_and_rescue
  # begin call not necessary 
  # raising and rescuing
end  

2.2 Raise

An exception can either be raised manually by the developer, using the raise command, or automatically by the language itself. Exceptions are raised automatically on incurring a syntactical error, like a variable not declared, a function not defined or an invalid math operation etc.

Based on your application, custom exception conditions can be catered to by manually raising errors. 

A very common use case would be a signup webpage that doesn’t submit the form until you have entered (and/or re-entered) a valid password. This sort of client-side proofing can save you from an unnecessary HTTP request to your server which might either affect database integrity or respond with an error message, which will need to be handled later. 

Let’s see how we can raise an exception in Ruby — 

begin  
    # if some condition is met or not met ->   
    raise "I am the error message!"
    # below lines are not executed    
end  

Here we are just raising an exception, not handling it (yet). This results in the program being terminated where the exception is raised, and as a result, the lines below are not executed. A language-generated error message is output as shown below — 

Ruby1.png

To prevent this, we can handle the exceptions in our own way, by using rescue blocks.

2.3 Rescue to the rescue

Fire.gif

Rescue blocks are like catch blocks that take control of the program after an exception has been raised. Raised exceptions can be followed by one or more rescue blocks to handle the exceptions based on their type. A generic rescue call looks like this — 

begin
    # if some condition is met or not met ->
    raise 'I am the error message!'

    puts 'I am not executed.'
rescue    
    puts 'Rescue to the rescue!'
end    

puts 'Amicably exiting ..'  

Once the exception is handled, execution resumes from after the begin block.

Ruby2.png

The rescue call defaults to a StandardError parameter. Rescue calls can however be made to handle specific exceptions. Some common Ruby errors (also common across other languages) are — 

  • IndexError
  • NameError
  • RangeError
  • TypeError
  • ZeroDivisionError
  • RuntimeError

Rescue blocks can be made to cater to certain types (classes) of exceptions.

In this way, raised exceptions can be followed by multiple rescue clauses, each responsible for handling a different type of exception. Let’s see how we can do this in code — 

begin  
  # error raised here 
rescue IndexError  
  # dealing in one way
rescue RangeError  
  # dealing in another way  
end 

Arguments can be used with the rescue clause to act as placeholders for the rescued exception — 

begin  
    raise 'I am the error message!'  
rescue StandardError => e  
    # inspecting the raised exception
    puts e.message

    # information about where the exception was raised  
    puts e.backtrace.inspect  
end 

Ruby3.png

The output tells how the exception was raised in line 2 of our code.

Specific types (classes) of exceptions can be raised even with custom error messages. For example —

begin
raise ZeroDivisionError, "Custom error message"
rescue ZeroDivisionError => e  
    # inspecting the raised exception
    puts e.message  
    puts e.backtrace.inspect
end 

Ruby4.png2.4 Else

Sometimes it’s possible that there was nothing to rescue from, that no exception was raised. The else clause can be used in this case as such — 

begin  
rescue StandardError => e  
    # inspecting the raised exception
    puts e.message  
    puts e.backtrace.inspect
else
    puts 'No exception to capture?'
end 

Ruby5.pngMagic variables

When exceptions are raised, Ruby assigns two variables — $! and $@ with information about the raised exception.

  • $! stores the exception message
  • $@ stores the backtrace
begin
raise ZeroDivisionError, "Custom error message"
rescue ZeroDivisionError => e
    # inspecting the raised exception
    puts $! # equivalent to e.message
    puts $@ # equivalent to e.backtrace.inspect
end 

Ruby6.png
2.5 Retry

Well, what if you don’t want to give up? You might want to keep trying till it works. 

undefined

This could be a common case when network connections are inconsistent. 

As I type this post on a Google document, at my home, on a poor internet connection, I can see the webpage continuously trying to keep me connected to the internet.

Till now we have seen how rescue blocks can be used to deal with what happens after an exception has occurred. The retry clause allows us to run the begin block again. This can come in handy after we have fixed what caused the exception to happen in the first place. 

Let’s see how retry can help, with a rather simple example. 

We know division of a number by zero is undefined. In the example below, we try to divide a by b (a / b). Initially the denominator, b is zero and therefore the division would raise a ZeroDivisionError. In the rescue block, we change the denominator to 1 and retry the whole thing.

a = 10
b = 0
begin
puts a/b
rescue ZeroDivisionError => e  
    # inspecting the raised exception
    puts e.message  
    puts e.backtrace.inspect
    b = 1
    retry
else
    puts 'No exception to capture?'
    puts 'Division was successful!'
end 

Ruby7.png

2.6 Ensure

Ensure is like the finally block that we have in other programming languages. It is there to always run at the end of each begin block. Regardless of any control sequence that the program ends up following, the ensure block is always executed at the end of it’s parent begin block. 

begin
    # if some condition is met or not met ->
    raise 'I am the error message!'
rescue    
    puts 'Rescue to the rescue!'
ensure
    puts 'Always executed at the end.'
end    

Ruby8.pngEnsure blocks are executed even if there isn’t a rescue block to handle a thrown exception. 

begin
    # if some condition is met or not met ->
    puts 'Begin!'
    raise 'I am the error message!'
ensure
    puts 'Executed even when there is no rescue clause'
end 

Ruby9.png

Note how in this case, even though the ensure block is executed at the end, the raised exception manifests as a runtime error after the ensure block.

Ensure blocks are generally used to free up any resources after an exception has been raised eg. closing database connections, closing file handlers etc. Without such blocks, the program abruptly terminates without clearing the resources.

Throw and catch in Ruby — lightweight alternatives

It’s not a good idea to exceptionally over-handle your code. Extensive raising and rescuing can take a toll on the system’s performance. 

Each time an exception is raised, a stack trace is built that helps in debugging the error. Too many of such handled exceptions can soon become a terrible bottleneck.

Turns out there is a lightweight alternative to the conventional approach that allows you to smoothly transfer program control out of complex nested blocks. Throw clauses and catch blocks in Ruby are linked through a common label name that both share.

throw :labelname
# ..
catch :labelname do
# ..
end

You can also use conditional throw calls as such — 

throw :labelname if a > b
# ..
catch :labelname do
# ..
end

Throw and catch in Ruby are quite different from conventional throw-catch mechanisms in other languages. In Ruby, they are used more for terminating execution quickly, and not as much for handling exceptions. 

It can come in handy when you are working with nested for loops and you want to get out of the computationally expensive mess when a condition is met, without having to use multiple break statements. Throw helps program control to move around swiftly to help programs execute faster, in a more efficient manner, compared to slow conventional begin/raise/rescue mechanisms that are more concerned about the raised exception, where it happened etc.

The way that throw and catch are used is that throw calls are made from inside catch blocks, transferring control from deep inside a nested construct, back to the level of the catch block in the code.

catch(:get_me_out) do
  # arbitrary nested for loop
  for a in 1..5 do
    for b in 1..5 do
      # arbitrary condition
      if a + b == 7
        # throwing to get out of the nested loops
        throw(:get_me_out)
      end
    end
  end
end

puts 'Good to go!'

Ruby10.png

A throw function can also be called with a custom message as an argument, which the catch block ends up returning after something is thrown.

thrown_msg = catch(:get_me_out) do

  # arbitrary nested for loop
  for a in 1..5 do
    for b in 1..5 do

      # arbitrary condition
      if a + b == 7

        # throwing to get out of the nested loops
        throw(:get_me_out, 'Inside this mess, get me out')
      end
    end
  end
end

puts thrown_msg
puts 'Good to go!'

Ruby11.png

Custom Exception class in Ruby

We can create our own Exception classes that cater to specific requirements in our projects. This can be done by inheriting from the provided StandardError class.

class MyCustomError < StandardError
end

raise MyCustomError

Ruby12.png

It’s good to have an error message to accompany the raised exception. We can do this by overriding the initialize method for our custom class with a default msg argument.

class MyCustomError < StandardError
  def initialize(msg="Custom error message")
    # to override the method
    super
  end
end

raise MyCustomError

Ruby13.png

Wrapping it up

wrap.gif

In this post, we looked at —

  • What is exception handling (in general)
  • Exception handling in Ruby —
    • Begin — end block
    • Raise
    • Rescue
    • Else
    • Retry
    • Ensure
  • Throw and catch in Ruby
  • Custom exception classes

All in all, we learnt how Ruby prefers a different terminology for dealing with exceptions. Except for the retry function, the begin / raise / rescue paradigm resonates with the conventional try / throw / catch setup used across most other programming languages. Throw and catch in Ruby however help in quickly getting yourself out of complex constructs. We also saw how we can create our application-specific custom Exception classes that inherit from the StandardError class.

Try ScoutAPM

Thanks for reading our blog. Now that you have a handle on handling exceptions, you need the best way to find them in your application.  Sign up here today for a free trial or contact us with any queries you might have  Want to show off Scout to your fellow developers? Click here for some free ScoutAPM swag.


The execution and the exception always go together. If you are opening a file, which does not exist, then if you did not handle this situation properly, then your program is considered to be of bad quality.

The program stops if an exception occurs. So exceptions are used to handle various type of errors, which may occur during a program execution and take appropriate action instead of halting program completely.

Ruby provide a nice mechanism to handle exceptions. We enclose the code that could raise an exception in a begin/end block and use rescue clauses to tell Ruby the types of exceptions we want to handle.

Syntax

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
else  
# Other exceptions
ensure
# Always will be executed
end

Everything from begin to rescue is protected. If an exception occurs during the execution of this block of code, control is passed to the block between rescue and end.

For each rescue clause in the begin block, Ruby compares the raised Exception against each of the parameters in turn. The match will succeed if the exception named in the rescue clause is the same as the type of the currently thrown exception, or is a superclass of that exception.

In an event that an exception does not match any of the error types specified, we are allowed to use an else clause after all the rescue clauses.

Example

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "File opened successfully"
   end
rescue
      file = STDIN
end
print file, "==", STDIN, "n"

This will produce the following result. You can see that STDIN is substituted to file because open failed.

#<IO:0xb7d16f84>==#<IO:0xb7d16f84>

Using retry Statement

You can capture an exception using rescue block and then use retry statement to execute begin block from the beginning.

Syntax

begin
   # Exceptions raised by this code will 
   # be caught by the following rescue clause
rescue
   # This block will capture all types of exceptions
   retry  # This will move control to the beginning of begin
end

Example

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "File opened successfully"
   end
rescue
   fname = "existant_file"
   retry
end

The following is the flow of the process −

  • An exception occurred at open.
  • Went to rescue. fname was re-assigned.
  • By retry went to the beginning of the begin.
  • This time file opens successfully.
  • Continued the essential process.

NOTE − Notice that if the file of re-substituted name does not exist this example code retries infinitely. Be careful if you use retry for an exception process.

Using raise Statement

You can use raise statement to raise an exception. The following method raises an exception whenever it’s called. It’s second message will be printed.

Syntax

raise 

OR

raise "Error Message" 

OR

raise ExceptionType, "Error Message"

OR

raise ExceptionType, "Error Message" condition

The first form simply re-raises the current exception (or a RuntimeError if there is no current exception). This is used in exception handlers that need to intercept an exception before passing it on.

The second form creates a new RuntimeError exception, setting its message to the given string. This exception is then raised up the call stack.

The third form uses the first argument to create an exception and then sets the associated message to the second argument.

The fourth form is similar to the third form but you can add any conditional statement like unless to raise an exception.

Example

#!/usr/bin/ruby

begin  
   puts 'I am before the raise.'  
   raise 'An error has occurred.'  
   puts 'I am after the raise.'  
rescue  
   puts 'I am rescued.'  
end  
puts 'I am after the begin block.'  

This will produce the following result −

I am before the raise.  
I am rescued.  
I am after the begin block.  

One more example showing the usage of raise

#!/usr/bin/ruby

begin  
   raise 'A test exception.'  
rescue Exception => e  
   puts e.message  
   puts e.backtrace.inspect  
end  

This will produce the following result −

A test exception.
["main.rb:4"]

Using ensure Statement

Sometimes, you need to guarantee that some processing is done at the end of a block of code, regardless of whether an exception was raised. For example, you may have a file open on entry to the block and you need to make sure it gets closed as the block exits.

The ensure clause does just this. ensure goes after the last rescue clause and contains a chunk of code that will always be executed as the block terminates. It doesn’t matter if the block exits normally, if it raises and rescues an exception, or if it is terminated by an uncaught exception, the ensure block will get run.

Syntax

begin 
   #.. process 
   #..raise exception
rescue 
   #.. handle error 
ensure 
   #.. finally ensure execution
   #.. This will always execute.
end

Example

begin
   raise 'A test exception.'
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
ensure
   puts "Ensuring execution"
end

This will produce the following result −

A test exception.
["main.rb:4"]
Ensuring execution

Using else Statement

If the else clause is present, it goes after the rescue clauses and before any ensure.

The body of an else clause is executed only if no exceptions are raised by the main body of code.

Syntax

begin 
   #.. process 
   #..raise exception
rescue 
   # .. handle error
else
   #.. executes if there is no exception
ensure 
   #.. finally ensure execution
   #.. This will always execute.
end

Example

begin
   # raise 'A test exception.'
   puts "I'm not raising exception"
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
else
   puts "Congratulations-- no errors!"
ensure
   puts "Ensuring execution"
end

This will produce the following result −

I'm not raising exception
Congratulations-- no errors!
Ensuring execution

Raised error message can be captured using $! variable.

Catch and Throw

While the exception mechanism of raise and rescue is great for abandoning the execution when things go wrong, it’s sometimes nice to be able to jump out of some deeply nested construct during normal processing. This is where catch and throw come in handy.

The catch defines a block that is labeled with the given name (which may be a Symbol or a String). The block is executed normally until a throw is encountered.

Syntax

throw :lablename
#.. this will not be executed
catch :lablename do
#.. matching catch will be executed after a throw is encountered.
end

OR

throw :lablename condition
#.. this will not be executed
catch :lablename do
#.. matching catch will be executed after a throw is encountered.
end

Example

The following example uses a throw to terminate interaction with the user if ‘!’ is typed in response to any prompt.

def promptAndGet(prompt)
   print prompt
   res = readline.chomp
   throw :quitRequested if res == "!"
   return res
end

catch :quitRequested do
   name = promptAndGet("Name: ")
   age = promptAndGet("Age: ")
   sex = promptAndGet("Sex: ")
   # ..
   # process information
end
promptAndGet("Name:")

You should try the above program on your machine because it needs manual interaction. This will produce the following result −

Name: Ruby on Rails
Age: 3
Sex: !
Name:Just Ruby

Class Exception

Ruby’s standard classes and modules raise exceptions. All the exception classes form a hierarchy, with the class Exception at the top. The next level contains seven different types −

  • Interrupt
  • NoMemoryError
  • SignalException
  • ScriptError
  • StandardError
  • SystemExit

There is one other exception at this level, Fatal, but the Ruby interpreter only uses this internally.

Both ScriptError and StandardError have a number of subclasses, but we do not need to go into the details here. The important thing is that if we create our own exception classes, they need to be subclasses of either class Exception or one of its descendants.

Let’s look at an example −

class FileSaveError < StandardError
   attr_reader :reason
   def initialize(reason)
      @reason = reason
   end
end

Now, look at the following example, which will use this exception −

File.open(path, "w") do |file|
begin
   # Write out the data ...
rescue
   # Something went wrong!
   raise FileSaveError.new($!)
end
end

The important line here is raise FileSaveError.new($!). We call raise to signal that an exception has occurred, passing it a new instance of FileSaveError, with the reason being that specific exception caused the writing of the data to fail.

Понравилась статья? Поделить с друзьями:
  • Ruby argument error
  • Ruantiblock update error another instance of update is already running
  • Ru store синтаксическая ошибка
  • Ru store ошибка разбора пакета
  • Ru store ошибка при синтаксическом анализе пакета