Catch error rails

The simple guide to exception handling in Ruby. ✓ Harden your app against unexpected errors ✓ Respond more effectively ✓ Adapt to any issue that may arise.

Exception Handling in Ruby

In Ruby, error handling works like this; all exceptions and errors are extensions of the Exception class. While this may seem intuitive, exception handling in Ruby is a touch more nuanced than you might expect thanks to the designed hierarchy of Ruby exceptions.

The begin-rescue

Similar to PHP’s try-catch handler, Ruby’s exception handling begins with the begin-rescue block. In a nutshell, the begin-rescue is a code block in Ruby that can be used to deal with raised exceptions without interrupting the Ruby program execution. In other words, you can begin to execute a block of code, and rescue any exceptions that are raised.

Rescuing Exceptions

In Ruby by default, begin-rescue rescues every instance of the StandardError class. This includes no method errors, type errors, runtime errors, and every custom error that is intended to be rescued within a Ruby application (see Raising Exceptions in Ruby for more information). To rescue every StandardError, simply wrap the designated section of code in a begin-rescue block:

begin
  # ...
rescue => e
  # ...
end

In Ruby when a StandardError exception is raised within the begin block, an instance of it will be passed to the rescue block as the variable e (for more information about the structure of Ruby’s Exception class, see Raising Exceptions in Ruby).

Rescuing Specific Exceptions

While rescuing every exception raised in your Ruby app is great for simplistic implementations—such as generalizing API error responses—best practice is to rescue for specific exceptions. To do this, let’s rewrite the generic Ruby begin-rescue block above to specifically rescue StandardError exceptions:

begin
  # ...
rescue StandardError => e
  # ...
end

Although the difference may be subtle, by following the rescue command with a class name, then only exceptions of the defined type will be rescued. For example, if we wanted to rescue all argument errors, we could structure our begin-rescue block like this:

begin
  # ...
rescue ArgumentError => e
  # ...
end

But, what if we want to rescue more than one exception type? Much like an if-elsif-else chain, a begin-rescue block can have multiple rescues, which when combined with a check for the StandardError class, allows you to logically adapt to any and all issues that may arise:

begin
  # ...
rescue ArgumentError => e
  # ...
rescue TypeError => e
  # ...
rescue => e
  # ...
end

Rescuing All Exceptions

While it may be tempting to rescue every child of the Exception class, it is generally considered bad practice due to the way the Ruby exception hierarchy is structured. The reason for this is that, while all Ruby exceptions and errors are an extension of the Exception class, many of them are reserved for use internally by Ruby. For example, SignalException::Interrupt is used to signal that Ctrl-C has been detected during the execution of a script.

If you were to rescue every child of the Exception class, then the Interrupt exception wouldn’t work as expected. That said, if you do want to rescue every exception that is raised in Ruby, the same begin-rescue block can be used to specifically rescue all Exception exceptions:

begin
  # ...
rescue Exception => e
  # ...
end

Using the above begin-rescue block in Ruby will rescue every exception and error that is raised, from interrupts to syntax errors, and even memory errors, so use it sparingly and with caution.

How to check Ruby syntax to identify exceptions

rescueclauses are used to tell Ruby which exception or types of exceptions we want to handle. The syntax for therescuestatement is:

begin
    # may raise an exception
rescue AnException
    # exception handler
rescue AnotherException
    # exception handler
else
    # other exceptions
ensure
    # always executed
end

The code betweenbeginandrescueis where a Ruby exception can occur. If an exception is encountered, the code inside therescueclause gets executed. For eachrescueclause, the raised Ruby exception is compared against each parameter and the match succeeds if the exception in the clause is the same as or a superclass of the thrown exception.

If the thrown Ruby exception does not match any of the specified exception types, theelse block gets executed. Theensureblock is always executed whether a Ruby exception occurs or not.

As an example:

#!/usr/bin/ruby
begin
   file = open("/tmp/myfile")
rescue Errno::ENOENT
   p "File not found"
else
   p "File opened"
end

In the above example, a file is attempted to be opened in thebeginblock. Therescueblock catches a “File not found” Ruby exception in case the file is not found at the location. If the file is found, theelseblock gets executed.

Running the above Ruby code produces the following result if the file is not found:

"File not found"

If the file is found, the following output is produced:

"File not found"

Exception Handling in Ruby on Rails

Generally speaking, the begin-rescue block works as intended in Ruby on Rails. That said, in order to better handle the specific use cases that can come up in the Rails architecture, additional methods have been made available for use within a Ruby on Rails application.

The rescue_from

The rescue_from directive is an exception handler that rescues exceptions raised in controller actions. The rescue_from directive rescues the specified exceptions raised within a controller, and reacts to those exceptions with a defined method. For example, the following controller rescues User::NotAuthorized exceptions and passes them to the deny_access() method:

class ApplicationController < ActionController::Base
  rescue_from User::NotAuthorized, with: :deny_access

  protected
    def deny_access(exception)
    # ...
    end
end

The advantage to rescue_from is that it abstracts the exception handling away from individual controller actions, and instead makes exception handling a requirement of the controller. This not only makes exception handling within controllers more readable, but also more regimented.

Exception Handling in Sinatra

While the additional exception handling within Ruby on Rails is focused on controller exceptions, Sinatra offers a few additional ways to deal with raised exceptions.

Logging Exceptions with dump_errors

Enabled by default, dump_errors is a Sinatra setting that allows exception backtraces to be written directly to STDERR. In the context of a development server, this information can be incredibly valuable, but might be more difficult to act upon in a production environment. When used in conjunction with traditional log aggregation and analysis techniques, however, this is a great way to collect exception data as it happens without needing to reproduce it.

Propagating Exceptions with raise_errors

By default, exceptions raised within Sinatra do not leave the application. What this means is that, when an exception is raised, it is rescued and mapped to internal error handlers. By enabling the raise_errors setting, these exceptions are raised outside of the application, allowing the server handler or Rack middleware to deal with exceptions.

Enable Classy Error Pages with show_exceptions

When working in a development environment, being able to quickly react to exceptions is crucial, which is why exception backtraces and environment information are on-screen by default in these environments. While this setting is turned off in production environments, it can be enabled or disabled by updating the show_exceptions setting.

Custom Error Handling

While Sinatra has built-in support for graceful error handling (error handler), it is sometimes desirable to write custom logic to handle raised errors. To do this, an error block can be used in the same way as a rescue block. For example, to rescue a User::NotAuthorized error, the following directive would work in Sinatra:

error User::NotAuthorized do
  # ...
end

Similarly, if we wanted to rescue all exceptions that are raised in a Sinatra application, we could use the following error handler:

error do
  # ...
end

Check ruby syntax

If you ever need to check the syntax of your Ruby code here is a great way to do so without executing the Ruby code.

This will check the syntax without executing the program:

ruby -c filename.rb

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

Программа останавливается, если возникает исключение. Таким образом, исключения используются для обработки различных типов ошибок, которые могут возникать во время выполнения программы, и необходимо предпринимать соответствующие действия, а не полностью останавливать программу.

Ruby обеспечивает хороший механизм обработки исключений. Мы прилагаем код, который может вызвать исключение в блоке begin/end и использовать предложения rescue, чтобы сообщить Ruby о типах исключений, которые мы хотим обработать.

Синтаксис

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
else  
# другие исключения
ensure
# Всегда будет выполняться
end

Все от begin до rescue защищено. Если во время выполнения этого блока кода возникает исключение, управление передается блоку между rescue и end.

Для каждого предложения rescue в begin Ruby сравнивает поднятое исключение с каждым из параметров по очереди. Совпадение завершится успешно, если исключение, указанное в предложении rescue, совпадает с типом создаваемого исключения или является суперклассом этого исключения.

В случае, если исключение не соответствует ни одному из указанных типов ошибок, нам разрешено использовать предложение else после всех предложений rescue.

Пример

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "Файл успешно открыт "
   end
rescue
      file = STDIN
end
print file, "==", STDIN, "n"

Это приведет к следующему результату. Вы можете видеть, что STDIN заменяется file, потому что произошла ошибка открытия.

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

Использование утверждения Retry

Вы можете захватить исключение, используя rescue, а затем использовать заявление retry для выполнения блока begin с самого начала.

Синтаксис

begin
 #Исключения в этом коде
 #поймал следующий пункт rescue
rescue
   # Этот блок будет захватывать Все типы исключений
   retry  # Это переместит управление в начало <i>begin</i>
end

Пример

#!/usr/bin/ruby

begin
   file = open("/unexistant_file")
   if file
      puts "Файл успешно открыт"
   end
rescue
   fname = "existant_file"
   retry
end

Ниже приводится поток процесса:

  • Исключение произошло при открытии.
  • Вызвано rescue. fname было переназначено.
  • retry указал на начало begin.
  • Этот файл открывается успешно.
  • Продолжал необходимый процесс.

Примечание

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

Использование выражения raise

Вы можете использовать заявление raise чтобы сгенерировать исключение. Следующий метод вызывает исключение всякий раз, когда он вызывается. Будет напечатано второе сообщение.

Синтаксис

raise 

OR

raise "Error Message" 

OR

raise ExceptionType, "Error Message"

OR

raise ExceptionType, "Error Message" condition

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

Вторая форма создает новое исключение RuntimeError, устанавливая его сообщение для данной строки. Это исключение затем поднимает стек вызовов.

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

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

Пример

#!/usr/bin/ruby

begin  
   puts 'Код перед raise.'  
   raise 'Произошла ошибка.'  
   puts 'Код после raise.'  
rescue  
   puts 'Код в rescued.'  
end  
puts 'Код после блока begin.'

Это приведет к следующему результату:

Код после raise.  
Код в rescued.  
Код после блока begin.  

Еще один пример, показывающий использование raise:

#!/usr/bin/ruby

begin  
   raise 'Тестовое исключение.'  
rescue Exception => e  
   puts e.message  
   puts e.backtrace.inspect  
end

Это приведет к следующему результату:

Тестовое исключение.
["main.rb:4"]

Использование инструкции ensure

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

Предложение ensure делает именно это. обеспечивается после последнего предложения rescue и содержит кусок кода, который всегда будет выполняться по завершении блока. Не имеет значения, нормально ли завершиться блок, если есть исключения raises и rescues, или если он завершается исключением, блок ensure будет запущен.

Синтаксис

begin 
   #.. процесс
   #..поднять исключение
rescue 
   #.. ошибка обработки 
ensure 
   #.. наконец, убедитесь в выполнении
   #.. Это всегда будет выполняться.
end

Пример

begin
   raise 'Тестовое исключение.'
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
ensure
   puts "Обеспечение выполнения"
end

Это приведет к следующему результату:

Тестовое исключение.
["main.rb:4"]
Обеспечение выполнения

Использование инструкции else

Если предложение else присутствует, оно выполняется после предложений rescue и до того, как оно будет выполнено.

Тело предложения else выполняется только в том случае, если в основной части кода не возникают исключения.

Синтаксис

begin 
   #.. процесс
   #.. вызывается исключение
rescue 
   # .. ошибка обработки
else
   #.. выполняется, если нет исключения
ensure 
   #.. наконец, убедитесь в выполнении
   #.. Это всегда будет выполняться.
end

Пример

begin
   # raise 'Тестовое исключение.'
   puts "Я не поднимаю исключение"
rescue Exception => e
   puts e.message
   puts e.backtrace.inspect
else
   puts "Поздравляем - ошибок нет!"
ensure
   puts "Обеспечение выполнения"
end

Это приведет к следующему результату:

Я не поднимаю исключение
Поздравляем - ошибок нет!
Обеспечение выполнения

Сообщение “Поднятая ошибка” можно записать с помощью переменной $!.

Catch и Throw

В то время как механизм исключения и rescue отлично подходит для отказа от выполнения, когда что-то идет не так, иногда бывает приятно выпрыгнуть из какой-то глубоко вложенной конструкции во время нормальной обработки. Это – то, где может пригодится catch и throw.

catch определяет блок, который помечен с данным именем (которое может быть символ или строка). Блок выполняется нормально до тех пор, пока не будет обнаружен throw.

Синтаксис

throw :lablename
#.. это не будет выполнено
catch :lablename do
#.. соответствующий catch будет выполняться после обнаружения throw.
end

OR

throw :lablename condition
#.. это не будет выполнено
catch :lablename do
#.. соответствующий catch будет выполняться после обнаружения throw.
end

Пример

В следующем примере используется throw для прекращения взаимодействия с пользователем, если ‘!’ набирается в ответ на любое приглашение.

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

catch :quitRequested do
   name = promptAndGet("Имя: ")
   age = promptAndGet("Возраст: ")
   sex = promptAndGet("Пол: ")
   # ..
   # process information
end
promptAndGet("Имя:")

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

Имя: Ruby on Rails
Возраст: 18
Пол: !
Имя: AndreyEx

Класс Exception

Стандартные классы и модули Ruby вызывают исключения. Все классы исключений образуют иерархию с классом Exception в верхней части. Следующий уровень содержит семь разных типов:

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

На этом уровне есть еще одно исключение, Fatal, но интерпретатор Ruby использует это только внутренне.

И ScriptError, и StandardError имеют ряд подклассов, но здесь нам не нужно вдаваться в подробности. Важно то, что если мы создаем собственные классы исключений, они должны быть подклассами любого класса Exception или одного из его потомков.

Давайте посмотрим на пример:

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

Теперь рассмотрим следующий пример, который будет использовать это исключение:

File.open(path, "w") do |file|
begin
   # Запишите данные ...
rescue
   # Что-то пошло не так!
   raise FileSaveError.new($!)
end
end

Важной линией здесь является поднять FileSaveError.new ($!). Мы вызываем raise, чтобы сигнализировать о том, что произошло исключение, передав ему новый экземпляр FileSaveError, по причине того, что конкретное исключение вызвало сбой записи данных.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Exceptions in Ruby

In Ruby, Exceptions are created using raise command:

1begin
2  raise "boom"
3end

If we execute that code then we will see the error:


Notice that Ruby is saying that it is a RuntimeError. Here is Ruby’s official
documentation about
RuntimeError. Above code
can also be written like this:

1raise RuntimeError, "boom"

As per Ruby’s documentation if when we do not mention any class while raising an
exception then by default it is RuntimeError class.

In Ruby all exceptions are subclasses of Exception class.

Hierarchy of Ruby Exception class

Ruby has lots of built in exceptions. Here is hierarchy of all Ruby’s
exceptions:

1Exception
2  NoMemoryError
3  ScriptError
4    LoadError
5    NotImplementedError
6    SyntaxError
7  SecurityError
8  SignalException
9    Interrupt
10  StandardError
11    ArgumentError
12      UncaughtThrowError
13    EncodingError
14    FiberError
15    IOError
16      EOFError
17    IndexError
18      KeyError
19      StopIteration
20    LocalJumpError
21    NameError
22      NoMethodError
23    RangeError
24      FloatDomainError
25    RegexpError
26    RuntimeError
27    SystemCallError
28      Errno::*
29    ThreadError
30    TypeError
31    ZeroDivisionError
32  SystemExit
33  SystemStackError

The rescue method catches a class and all its subclasses

1begin
2  do_something
3rescue NameError
4end

Here we are rescuing all NameError exceptions. However NoMethodError will
also be rescued because NoMethodError is a subclass of NameError.

For example, consider a api_exceptions.rb file where we are rescuing from all
the exceptions like so:

1module ApiExceptions
2  rescue_from StandardError, with: :handle_api_exception
3  rescue_from ActiveRecord::RecordNotUnique, with: :handle_record_not_unique_exception
4
5  def handle_api_exception(exception)
6    case exception
7    # handles the exceptions
8  end
9
10  def handle_record_not_unique_exception(exception)
11    # handles record not unique exception
12  end
13end

The RecordNotUnique exception is a child class of StandardError. When we
rescue from StandardError all the child exception classes will also be caught.
So the rescue_from ActiveRecord::RecordNotUnique in the above example is
redundant. The code will never reach this line because the RecordNotUnique
exception will already be caught by the rescue_from StandardError statement.

The correct way of rescuing the errors will be, like so:

1module ApiExceptions
2  rescue_from StandardError, with: :handle_api_exception
3
4  def handle_api_exception(exception)
5    case exception
6
7    when ActiveRecord::RecordNotFound
8      handle_record_not_unique_exception(exception)
9
10    # handles the other exceptions
11  end
12
13  def handle_record_not_unique_exception(exception)
14    # handles record not unique exception
15  end
16end

In the above example, we are rescuing StandardError and all its children
classes with handle_api_exception method. And inside handle_api_exception
method case is used to handle
the required child exception classes specifically.

Certain exceptions and when they are raised

  • Pundit::NotAuthorizedError is raised when any authorization error from
    Pundit gem raises and we
    can handle it with 403 or forbidden status.

  • ActionController::ParameterMissing is raised when a required parameter is
    missing. Consider a case when we have strong params defined for a post
    request and those params are missing in the request header. We can handle this
    via 500 or internal_server_error status.

  • ActiveRecord::RecordNotFound is raised when Rails does not find any
    record. When we use find method to search for a record with the provided
    id in params and the id is mistaken or the record is missing from DB,
    Rails raises ActiveRecord::RecordNotFound exception. We can handle this
    with a 404 or not_found status.

  • ActiveRecord::RecordInvalid is raised on failure of validations declared in
    model for any record creation or updation. Let’s say we have some email
    validation declared in model and the input email does not match with the given
    Regex pattern Rails raises ActiveRecord::RecordInvalid exception. We can
    handle this with a 422 or unprocessable_entity status.

  • ActiveRecord::RecordNotUnique is raised when a record cannot be inserted or
    updated because it would violate a uniqueness constraint from DB. We can
    handle this with unprocessable_entity or 422 status.

  • PG::NotNullViolation: ERROR: null value in column «name» of relation «users» violates not-null constraint
    for missing mandatory input. We should handle the database errors starting
    with PG:: or SQLite3:: with internal_server_error or 500 status. If we
    keep this as unprocessable_entity then it won’t raise Honeybadger issues,
    meaning it will log the error silently. These errors are very rare and we
    should be getting notified of these errors by Honeybadger in Github.

Raising error using class

Following two lines do the same thing:

1raise "boom"
2raise RuntimeError, "boom"

We can raise exceptions of a particular class by stating the name of that
exception class:

1raise ArgumentError, "two arguments are needed"
2raise LoadError, "file not found"

Default rescue is StandardError

rescue without any argument is same as rescuing StandardError:


Above statement is same as the one given below:

1begin
2rescue StandardError
3end

Catching multiple types of exceptions in one shot

We can catch multiple types of exceptions in one statement:

1begin
2rescue ArgumentError,NameError
3end

Catching exception in a variable

We can catch exception in a variable like this:

1begin
2rescue StandardError => e
3end

Here e is an exception object. The three main things we like to get from an
exception object are «class name», «message» and «backtrace».

Let’s print all the three values:

1begin
2   raise "boom"
3rescue StandardError => e
4  puts "Exception class is #{e.class.name}"
5  puts "Exception message is #{e.message}"
6  puts "Exception backtrace is #{ e.backtrace}"
7end

Custom exceptions

Sometimes we need custom exceptions. Creating custom exceptions is easy:

1class NotAuthorizedError < StandardError
2end
3
4raise NotAuthorizedError.new("You are not authorized to edit record")

NotAuthorizedError is a regular Ruby class. We can add more attributes to it
if we want:

1class NotAuthorizedError < StandardError
2  attr_reader :account_id
3
4  def initialize(message, account_id)
5    #invoke the constructor of parent to set the message
6    super(message)
7
8    @account_id = account_id
9  end
10end
11
12raise NotAuthorizedError.new("Not authorized", 171)

rescue nil

Sometimes we see code like this:


The above code is equivalent to the following code:

1begin
2  do_something
3rescue
4  nil
5end

The above code can also be written like so, since by default StandardError is
raised:

1begin
2  do_something
3rescue StandardError
4  nil
5end

Exception handling in Ruby on Rails using rescue_from

A typical controller could look like this:

1class ArticlesController < ApplicationController
2  def show
3    @article = Article.find(params[:id])
4  rescue ActiveRecord::RecordNotFound
5    render_404
6  end
7
8  def edit
9    @article = Article.find(params[:id])
10  rescue ActiveRecord::RecordNotFound
11    render_404
12  end
13end

We can use
rescue_from
to catch the exception.

The rescue_from directive is an exception handler that rescues the specified
exceptions raised within controller actions and reacts to those exceptions with
a defined method.

For example, the following controller rescues ActiveRecord::RecordNotFound
exceptions and passes them to the render_404 method:

1class ApplicationController < ActionController::Base
2  rescue_from ActiveRecord::RecordNotFound, with: :render_404
3
4  def render_404
5  end
6end
7
8class ArticlesController < ApplicationController
9  def show
10    @article = Article.find(params[:id])
11  end
12
13  def edit
14    @article = Article.find(params[:id])
15  end
16end

The advantage to rescue_from is that it abstracts the exception handling away
from individual controller actions, and instead makes exception handling a
requirement of the controller.

The rescue_from directive not only makes exception handling within controllers
more readable, but also more regimented.

Rescuing from specific exception

Ruby’s Exception is the parent class to all errors. So one might be tempted to
always rescue from this exception class and get the «job» done. But DON’T!

Exception includes the class of errors that can occur outside your
application. Things like memory errors, or SignalException::Interrupt(sent
when you manually quit your application by hitting Control-C), etc. These are
the errors that you don’t want to catch in your application as they are
generally serious and related to external factors. Rescuing the Exception
class can cause very unexpected behaviour.

StandardError is the parent of most Ruby and Rails errors. If you catch
StandardError you’re not introducing the problems of rescuing Exception, but
it is not a great idea either. Rescuing all application-level errors might cover
up unrelated bugs that you don’t know about.

The safest approach is to rescue the error(or errors) you are expecting and deal
with the consequences of that error inside the rescue block.

In the event of an unexpected error in your application you want to know that a
new error has occurred and deal with the consequences of that new error inside
its own rescue block.

Being specific with rescue means your code doesn’t accidentally swallow new
errors. You avoid subtle hidden errors that lead to unexpected behaviour for
your users and bug hunting for you.

Do not use exception as control flow

Let’s look at the following code:

1class QuizController < ApplicationController
2  def load_quiz
3    @quiz = current_user.quizzes.find(params[:id])
4  rescue ActiveRecord::RecordNotFound
5    format.json { render status: :not_found, json: { error: "Quiz not found"}}
6  end
7end

In the above code when quiz id is not found then an exception is raised and then
that exception is immediately caught.

Here the code is using exception as a control flow mechanism. What it means is
that the code is aware that such an exception could be raised and is prepared to
deal with it.

The another way to deal with such a situation would be to not raise the
exception in the first place. Here is an alternative version where code will not
be raising any exception:

1class QuizController < ApplicationController
2  def load_quiz
3    @quiz = current_user.quizzes.find_by_id(params[:id])
4    unless @quiz
5      format.json { render status: :not_found, json: { error: "Quiz not found"}}
6    end
7  end
8end

In the above case instead of using find code is using find_by_id which would
not raise an exception in case the quiz id is not found.

In Ruby world we like to say that an exception should be an exceptional
case
. Exceptional case could be database is down or there is some network
error. Exception can happen anytime but in this case code is not using catching
an exception as a control flow.

Long time ago in the software engineering world GOTO was used a lot. Later
Edsger W. Dijkstra wrote a
famous letter
Go To Statement Considered Harmful.
Today it is a well established that using GOTO is indeed harmful.

Many consider using Exception as a control flow similar to using GOTO since when
an exception is raised it breaks all design pattern and exception starts flowing
through the stack. The first one to capture the exception gets the control of
the software. This is very close to how GOTO works. In Ruby world it is well
established practice to
not to use Exception as a control flow.

Using bang methods in controller actions

But, just like everything in software engineering, the suggestion we had made in
last section also has some exceptions. Like in controllers etc, we should be
trying our level best to keep controllers as skinny as possible. So adding
repetitive unless or if statements as a replacement for rescue statements
won’t scale. Thus in such cases, what we should do is use the bang(!) versions
of ActiveRecord methods, like create!, update!, destroy! or save! within
the controller actions. This would raise an exception in case there’s a failure.

But where to handle theses exceptions? Well, in the chapter where we had cleaned
up the application controller, we had added a concern named ApiExceptions.
This concern is included in the ApplicationController. Which means all other
controllers will be having access to the methods defined in that concern. In
that concern we have several rescue_from statements, which handles specific
exceptions.

Thus in our controller, we could write something like so, and both the success
and failure cases will be handled:

1class QuizController < ApplicationController
2  before_action :load_quiz!, only: %i[update]
3
4  def update
5    @quiz.update!(quiz_params) # control wouldn't even reach here if exception was raised in load_quiz!
6    respond_with_success(t("successfully_created", entity: "Quiz")) # control will only reach here if the above statement didn't raise any exception
7  end
8
9  private
10
11    def load_quiz!
12      @quiz = current_user.quizzes.find_by!(id: params[:id])
13    end
14end

Notice how we have named the method as load_quiz! with a bang(!) rather than
simply load_quiz? It’s to denote that the particular method has the potential
to raise an exception. If no record is found by the find_by! method then a
ActiveRecord::RecordNotFound exception will be raised.

There is a problem in the above code block, which has to deal with code
conventions that we follow in BigBinary. That’s, if we are querying using the
id attribute only then we should use the find method over find_by! because
in the find method we can directly pass the id value without defining any
key and it makes the code cleaner. If for a given id no record is found by
find method then the same exception will be raised that is
ActiveRecord::RecordNotFound.

So, in the above code block the load_quiz! method needs to be updated like so:

1def load_quiz!
2  @quiz = current_user.quizzes.find(id)
3end

There is nothing to commit in this chapter since all we had done was
learning the basics of exception handling in Ruby.

Are you sick and tired of handling endless exceptions, writing custom logic to handle bad API requests and serializing the same errors over and over?

What if I told you there was a way to abstract away messy and repetitive error raising and response rendering in your Rails API? A way for you to write just one line of code (okay, two lines of code) to catch and serialize any error your API needs to handle? All for the low low price of just $19.99!

Okay, I’m kidding about that last part. This is not an infomercial.

Although Liz Lemon makes the snuggie (sorry, «slanket») look so good, just saying.

In this post, we’ll come to recognize the repetitive nature of API error response rendering and implement an abstract pattern to DRY up our code. How? We’ll define a set of custom errors, all subclassed under the same parent and tell the code that handles fetching data for our various endpoints to raise these errors. Then, with just a few simple lines in a parent controller, we’ll rescue any instance of this family of errors, rendering a serialized version of the raised exception, thus taking any error handling logic out of our individual endpoints.

I’m so excited. Let’s get started!

Recognizing the Repetition in API Error Response Rendering

The API

For this post, we’ll imagine that we’re working on a Rails API that serves data to a client e-commerce application. Authenticated users can make requests to view their past purchases and to make a purchase, among other things.

We’ll say that we have the following endpoints:

POST '/purchases'
GET '/purchases'

Any robust API will of course come with specs.

API Specs

Our specs look something like this:

Purchases

Request
GET api/v1/purchases
# params
{
  start_date: " ",
  end_date: " "
}
Success Response
# body
{ 
  status: "success",
  data: {
    items: [
      {
        id: 1,
        name: "rice cooker", 
        description: "really great for cooking rice",
        price: 14.95,
        sale_date: "2016-12-31"
      },
      ...
    ]
  }
}
# headers 

{"Authorization" => "Bearer <token>"}
Error Response
{
  status: "error",
  message: " ",
  code: " "
}
code message
3000 Can’t find purchases without start and end date

Yes, I’ve decided querying purchases requires a date range. I’m feeling picky.

Request
POST api/v1/purchases
# params
{
  item_id: 2
}
Success Response
# body
{ 
  status: "success",
  data: {
    purchase_id: 42,
    item_id: 2
    purchase_status: "complete"
  }
}
# headers 

{"Authorization" => "Bearer <token>"}
Error Response
{
  status: "error",
  message: " ",
  code: " "
}
code message
4000 item_id is required to make a purchase

Error Code Pattern

With just a few endpoint specs, we can see that there is a lot of shared behavior. For the GET /purchases request and POST /purchases requests, we have two specific error scenarios. BUT, in both of the cases in which we need to respond with an error, the response format is exactly the same. It is only the content of the code and message keys of our response body that needs to change.

Let’s take a look at what this error handling could look like in our API controllers.

API Controllers

# app/controllers/api/v1/purchases_controller.rb
module Api
  module V1
    class PurchasesController < ApplicationController
      def index
        if params[:start_date] && params[:end_date]
          render json: current_user.purchases
        else
          render json: {status: "error", code: 3000, message: "Can't find purchases without start and end date"}
        end
      end
 
      def create
        if params[:item_id]
          purchase = Purchase.create(item_id: params[:item_id], user_id: current_user.id)
          render json: purchase
        else
          render json: {status: "error", code: 4000, message: "item_id is required to make a purchase}
        end
      end  
    end
  end
end

Both of our example endpoints contain error rendering logic and they are responsible for composing the error to be rendered.

This is repetitious, and will only become more so as we build additional API endpoints. Further, we’re failing to manage our error generation in a centralized away. Instead creating individual error JSON packages whenever we need them.

Let’s clean this up. We’ll start by building a set of custom error classes, all of which will inherit from the same parent.

Custom Error Classes

All of our custom error classes will be subclassed under ApiExceptions::BaseException. This base class will contain our centralized error code map. We’ll put our custom error classes in the lib/ folder.

# lib/api_exceptions/base_exception.rb

module ApiExceptions
  class BaseException < StandardError
    include ActiveModel::Serialization
    attr_reader :status, :code, :message

    ERROR_DESCRIPTION = Proc.new {|code, message| {status: "error | failure", code: code, message: message}}
    ERROR_CODE_MAP = {
      "PurchaseError::MissingDatesError" =>
        ERROR_DESCRIPTION.call(3000, "Can't find purchases without start and end date"),
      "PurchaseError::ItemNotFound" =>
        ERROR_DESCRIPTION.call(4000, "item_id is required to make a purchase")
    }

    def initialize
      error_type = self.class.name.scan(/ApiExceptions::(.*)/).flatten.first
      ApiExceptions::BaseException::ERROR_CODE_MAP
        .fetch(error_type, {}).each do |attr, value|
          instance_variable_set("@#{attr}".to_sym, value)
      end
    end
  end
end

We’ve done a few things here.

  • Inherit BaseException from StandardError, so that instances of our class can be raised and rescued.
  • Define an error map that will call on a proc to generate the correct error code and message.
  • Created attr_readers for the attributes we want to serialize
  • Included ActiveModel::Serialization so that instances of our class can be serialized by Active Model Serializer.
  • Defined an #initialize method that will be called by all of our custom error child classes. When this method runs, each child class will use the error map to set the correct values for the @status, @code and @message variables.

Now we’ll go ahead and define our custom error classes, as mapped out in our error map.

# lib/api_exceptions/purchase_error.rb

module ApiExceptions
  class PurchaseError < ApiExceptions::BaseException
  end
end
# lib/api_exceptions/purchase_error/missing_dates_error.rb

module ApiExceptions
  class PurchaseError < ApiExceptions::BaseException
    class MissingDatesError < ApiExceptions::PurchaseError
    end
  end
end
# lib/api_exceptions/purchase_error/item_not_found.rb

module ApiExceptions
  class PurchaseError < ApiExceptions::BaseException
    class ItemNotFound < ApiExceptions::PurchaseError
    end
  end
end

Now that are custom error classes are defined, we’re ready to refactor our controller.

Refactoring The Controller

For this refactor, we’ll just focus on applying our new pattern to a single endpoint, since the same pattern can be applied again and again. We’ll take a look at the POST /purchases request, handled by PurchasesController#create

Instead of handling our login directly in the controller action, we’ll build a service to validate the presence of item_id. The service should raise our new custom ApiExceptions::PurchaseError::ItemNotFound if there is no item_id in the params.

module Api 
  module V1
    class PurchasesController < ApplicationController
      ...
      def create
        purchase_generator = PurchaseGenerator.new(user_id: current_user.id, item_id: params[:item_id])
        render json: purchase_generator
      end
    end
  end
end

Our service is kind of like a service-model hybrid. It exists to do a job for us––generate a purchase––but it also needs a validation and it will be serialized as the response body to our API request. For this reason, we’ll define it in app/models

# app/models

class PurchaseGenerator
  include ActiveModel::Serialization
  validates_with PurchaseGeneratorValidator
  attr_reader :purchase, :user_id, :item_id
  
  def initialize(user_id:, item_id:)
    @user_id = user_id
    @item_id = item_id
    @purchase = Purchase.create(user_id: user_id, item_id: item_id) if valid?
  end
end

Now, let’s build our custom validator to check for the presence of item_id and raise our error if it is not there.

class PostHandlerValidator < ActiveModel::Validator
  def validate(record)
    validate_item_id
  end

  def validate_item_id
    raise ApiExceptions::PurchaseError::ItemNotFound.new unless record.item_id
  end
end

This custom validator will be called with the #valid? method runs.

So, the very simple code in our Purchases Controller will raise the appropriate error if necessary, without us having to write any control flow in the controller itself.

But, you may be wondering, how will we rescue or handle this error and render the serialized error?

Universal Error Rescuing and Response Rendering

This part is really cool. With the following line in our Application Controller, we can rescue *any error subclassed under ApiExceptions::BaseException:

class ApplicationController < ActionController::Base
  rescue_from ApiExceptions::BaseException, 
    :with => :render_error_response
end

This line will rescue any such errors by calling on a method render_error_response, which we’ll define here in moment, and passing that method the error that was raised.

all our render_error_response method has to do and render that error as JSON.

class ApplicationController < ActionController::Base
  rescue_from ApiExceptions::BaseException, 
    :with => :render_error_response
  ...

  def render_error_response(error)
    render json: error, serializer: ApiExceptionsSerializer, status: 200
  end
end

Our ApiExceptionSerializer is super simple:

class ApiExceptionSerializer < ActiveModel::Serializer
  attributes :status, :code, :message
end

And that’s it! We’ve gained super-clean controller actions that don’t implement any control flow and a centralized error creation and serialization system.

Let’s recap before you go.

Conclusion

We recognized that, in an API, we want to follow a strong set of conventions when it comes to rendering error responses. This can lead to repetitive controller code and an endlessly growing and scattered list of error message definitions.

To eliminate these very upsetting issues, we did the following:

  • Built a family of custom error classes, all of which inherit from the same parent and are namespaced under ApiExceptions.
  • Moved our error-checking control flow logic out of the controller actions, and into a custom model.
  • Validated that model with a custom validator that raises the appropriate custom error instance when necessary.
  • Taught our Application Controller to rescue any exceptions that inherit from ApiExceptions::BaseException by rendering as JSON the raised error, with the help of our custom ApiExceptionSerializer.

Keep in mind that the particular approach of designing a custom model with a custom validator to raise our custom error is flexible. The universally applicable part of this pattern is that we can build services to raise necessary errors and call on these services in our controller actions, thus keeping error handling and raising log out of individual controller actions entirely.

Модульный подход к обработке ошибок в Rails.

Закон Мерфи:

Как гласит закон Мерфи, все, что может пойти не так, пойдет не так, поэтому важно быть к этому готовым. Это применимо везде, даже в разработке программного обеспечения. Приложение, которое мы разрабатываем, должно быть достаточно надежным, чтобы справиться с этим. Другими словами, он должен быть устойчивым. Это именно то, о чем этот пост в блоге.

Все, что может пойти не так, пойдет не так.

— Закон Мерфи

В типичном рабочем процессе Rails мы обрабатываем ошибки на уровне контроллера. Допустим, вы пишете API с помощью Rails. Рассмотрим следующий метод контроллера для визуализации пользовательского JSON.

Когда пользовательский объект найден, он отображает его как json, в противном случае отображает ошибку json. Это типичный способ написания метода show — это Rails. Но вот в чем загвоздка. Если запись пользователя не найдена, она не попадает в блок else, а отображает резервный контент 500.html. Что ж, это было неожиданно. Это связано с тем, что если запись не найдена, возникает ошибка RecordNotFound. То же самое и с find_by! или любые методы поиска на ура.

Исключение! = Ошибка

Прежде чем мы приступим к исправлению ошибок, нам нужно понять кое-что важное. Как видно из приведенного выше примера, мы получаем ошибку ActiveRecord :: RecordNotFound. Блок try catch в ruby ​​будет выглядеть примерно так, что отлично работает.

Но когда вы хотите спастись от всех исключений, действительно важно знать разницу между исключениями и ошибками в Ruby. Никогда не выполнять восстановление из исключений. Он пытается обработать каждое исключение, наследуемое от класса Exception, и в конечном итоге останавливает выполнение.

Вместо этого нам нужно спастись от StandardError. Вот отличное сообщение в блоге, объясняющее разницу http://blog.honeybadger.io/ruby-exception-vs-standarderror-whats-the-difference/.

Спасение

Для обработки ошибок мы можем использовать блок спасения. Блок восстановления аналогичен блоку try..catch, если вы из мира Java. Вот тот же пример со спасательным блоком.

При таком подходе ошибки устраняются в методах контроллера. Хотя это работает отлично, это может быть не лучший подход для обработки ошибок. Вот несколько причин рассмотреть альтернативный подход.

  1. Толстые контроллеры: обязательно прочтите эту отличную статью Thoughtbots https://robots.oughttbot.com/skinny-controllers-skinny-models.
  2. Принцип DRY: мы просто повторяем блокировку ошибки в разных местах, что противоречит принципу DRY (не повторяйся).
  3. Ремонтопригодность: сложнее поддерживать код. Изменения ошибки, такие как формат, повлекут за собой серьезные изменения.

Альтернативный подход — переместить блок обработки ошибок в ApplicationController. Более чистый подход — написать модуль обработчика ошибок.

Обработка ошибок — модульный подход

Чтобы обрабатывать ошибки в одном месте, наш первый вариант — написать в ApplicationController. Но лучше всего отделить его от логики приложения.

Давайте создадим модуль, который обрабатывает ошибки на глобальном уровне. Создайте модуль ErrorHandler (error_handler.rb) и поместите его в папку lib / error (или в другое место для загрузки), а затем включите его в наш ApplicationController.

Важно: загрузите модуль ошибок при запуске приложения, указав его в config / application.rb.

Примечание. Я использую несколько вспомогательных классов для рендеринга вывода json. Вы можете проверить это здесь.

Прежде чем приступить к работе с модулем error_handler, вот действительно интересная статья о модулях, которую вам обязательно стоит прочитать. Если вы заметили, что метод self.included в модуле работает так же, как если бы он был помещен в исходный класс. Поэтому все, что нам нужно сделать, это включить модуль ErrorHandler в ApplicationController.

Давайте проведем рефакторинг ErrorModule для размещения нескольких блоков обработки ошибок. Так он выглядит намного чище.

Если вы заметили ошибку ActiveRecord: RecordNotFound, она также наследует StandardError. Поскольку у нас есть механизм спасения, мы получаем: record_not_found. Блок StandardError действует как резервный механизм, который обрабатывает все ошибки.

Определите собственное исключение.

Мы также можем определить наши собственные классы ошибок, которые наследуются от StandardError. Для простоты мы можем создать класс CustomError, который содержит общие переменные и методы для всех определенных пользователем классов ошибок. Теперь наша UserDefinedError расширяет CustomError.

Мы можем переопределить методы, специфичные для каждой ошибки. Например, NotVisibleError расширяет CustomError. Как вы могли заметить, мы переопределяем error_message.

Для обработки всех ошибок, определенных пользователем, все, что нам нужно сделать, это спастись от CustomError. Мы также можем спастись от конкретной ошибки, если хотим обработать ее по-другому.

404 и 500

Вы можете обрабатывать распространенные исключения, такие как 404 и 500, хотя это полностью зависит от разработчика. Нам нужно создать для него отдельный класс контроллера, ErrorsController.

Скажите Rails использовать маршруты для разрешения исключений. Нам просто нужно добавить следующую строку в application.rb.

config.exceptions_app = routes

Теперь исключения 404 возвращаются к errors # not_found, а 500 — к errors # internal_server_error.

Заключительные примечания

Модульный подход — это способ обработки ошибок в Rails. Всякий раз, когда мы хотим изменить конкретное сообщение / формат об ошибке, нам просто нужно изменить его в одном месте. При таком подходе мы также отделяем логику приложения от обработки ошибок, тем самым делая Контроллеры Slick вместо Fat.

В Rails лучше всего использовать Skinny Controllers & Models .

Вот полный Исходный код для модульного подхода к обработке ошибок. Пожалуйста, нажмите кнопку «Рекомендовать», если вы сочли это полезным. И, как всегда, не стесняйтесь отвечать, если у вас есть сомнения. Ваше здоровье!

Способ 1: http://www.uedidea.com/rails%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86.html

def add_to_cart
product = Product.find(params[:id])
 спасение ActiveRecord :: RecordNotFound #intercept Product.find () исключение
 logger.error («Попытка получить доступ к недопустимому продукту # {params [: id]}») # Журнал
 flash [: note] = "Недопустимый продукт" # примечание Информация о напоминании магазина
 redirect_to: action => "index" # Перейти к индексу
end

begin
   @user.destroy
    flash[:notice] = "User #{@user.name} deleted"
 Rescue Exception => e #catch, если исключение происходит в приведенном выше выполнении кода
   flash[:notice] = e.message
end
begin
  raise ArgumentError, &quot;Bad data&quot;
rescue => err 
  puts err 
ensure
... # Выполнить очистку 
end  

 

Метод 2: around_filter

http://hlee.iteye.com/blog/323025

    around_filter :rescue_record_not_found     
        
    def rescue_record_not_found     
      begin     
        yield     
      rescue ActiveRecord::RecordNotFound     
        render :file => "#{RAILS_ROOT}/public/404.html"    
      end     
    end    

 

Хле также упомянул в статье, что это можно сделать:

    rescue_from ActiveRecord::RecordNotFound, with => :rescue_record_not_found     
        
    def rescue_record_not_found     
      render :file => "#{RAILS_ROOT}/public/404.html"    
    end    

 

Способ 3: rescue_action_in_public

Ссылка: http://202.199.224.30/post/article/7

    def rescue_action_in_public(exception)      
        logger.error("rescue_action_in_public executed")      
        case exception      
        when ActiveRecord::RecordNotFound, ::ActionController::RoutingError,        
          ::ActionController::UnknownAction      
          logger.error("404 displayed")      
          render(:file  => "#{RAILS_ROOT}/public/404.html",      
          :status   => "404 Not Found")      
        else      
          logger.error("500 displayed")      
          render(:file  => "#{RAILS_ROOT}/public/500.html",      
          :status   => "500 Error")      
    #      SystemNotifier.deliver_exception_notification(self, request,        
    #                                                    exception)      
        end      
      end    

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

code

config.action_controller.consider_all_requests_local = false   
 

Если необходимо добавить локальный доступ (app / controllers / application.rb):

code

def local_request? 
  false 
end  

 

Ссылка на тип ошибки:

    DEFAULT_RESCUE_RESPONSE = :internal_server_error  
    DEFAULT_RESCUE_RESPONSES = {  
      'ActionController::RoutingError'             => :not_found,  
      'ActionController::UnknownAction'            => :not_found,  
      'ActiveRecord::RecordNotFound'               => :not_found,  
      'ActiveRecord::StaleObjectError'             => :conflict,  
      'ActiveRecord::RecordInvalid'                => :unprocessable_entity,  
      'ActiveRecord::RecordNotSaved'               => :unprocessable_entity,  
      'ActionController::MethodNotAllowed'         => :method_not_allowed,  
      'ActionController::NotImplemented'           => :not_implemented,  
      'ActionController::InvalidAuthenticityToken' => :unprocessable_entity  
    }  
      
    DEFAULT_RESCUE_TEMPLATE = 'diagnostics'  
    DEFAULT_RESCUE_TEMPLATES = {  
      'ActionView::MissingTemplate'       => 'missing_template',  
      'ActionController::RoutingError'    => 'routing_error',  
      'ActionController::UnknownAction'   => 'unknown_action',  
      'ActionView::TemplateError'         => 'template_error'  
    }  

 

Прочитайте обсуждение в этом посте еще раз: http://www.iteye.com/topic/708334
Обработка ошибок в рельсах требует дальнейшего изучения.

There are various ways to rescue from exceptions raised in Rails App.

The most basic is

begin
.........
rescue NameOfException => exc
   logger.error("Message for the log file #{exc.message}")
   flash[:notice] = "Store error message"
   redirect_to(:action => 'index')
end

Or you can render a static HTML file namely public/401.html or public/400.html.erb

rescue NameOfException => exc
   logger.error("Message for the log file #{exc.message}")
   flash[:notice] = "Store error message"
   render file: "#{Rails.root.to_s}/public/401.html", status: :unauthorized
end

 Global Exception Handling in Rails

To handle globally (in application_controller.rb) or scoped to specific controller you can use the following syntax:

rescue_from CanCan::AccessDenied do
 render file: "#{Rails.root.to_s}/public/401.html", status: :unauthorized
end
rescue_from ActiveRecord::RecordNotFound do |exception|
 # It show the error in development mode
 throw exception if Rails.env === 'development'
 render file: "#{Rails.root.to_s}/public/400.html", status: :bad_request
end

or

if params[:url_code] && @pact.nil?
  raise Pundit::NotAuthorizedError,
        "You are not authorized to perform this action."
else

you can pass the error message like in the above example

syntax is

raise ExceptionClass, "Context specific error message"

and you can access this message in the exception handling method param.

rescue_from ExceptionClass, with: :oops_something_happened

def oops_something_happened(error = OpenStruct.new({message: nil}))
  flash[:error] = error.message || "You are not authorized to perform this action."
  redirect_to(request.referrer || root_path)
end

More Better Way:

To change this behavior of Rails to handle specific type of exception we need to modify the application’s config file. /config/application.rb

config.action_dispatch.rescue_responses["ProductsController::Forbidden"] = :forbidden
config.action_dispatch.rescue_responses["ActiveRecord::RecordNotFound"] = :forbidden

Here we set config.action_dispatch.rescue_responses which is a hash where the key represents the name of the exception that we want to handle and the value is the status code, in this case :forbidden (we can use the status codes that Rack uses instead of a numeric HTTP status). To learn more about how Rack handles status codes take a look at the documentation for Rack::Utils where we’ll find the names for each status code and how it’s converted to a symbol. If we visit the a product’s page now after restarting our app we’ll see a 403.html error page.

Next we’ll show you some of the exceptions that Rails maps by default. For example if we trigger a route that doesn’t exist we’ll see a 404 error instead of a 500. Most of these are defined in the Rails source code in the ExceptionWrapper class. This class sets the default rescue_responses hash that we configured earlier and one of the values set is ActionController::RoutingError which is set to :not_found. This is what we see in the application with the 404 status.

Note:

For this feature you must have files like 400.html, 404.html, etc inside the public dir.

Catch and Throw in Ruby

Jan 24, 2012

In my last post I had a look at handling exceptions in Ruby and the functionality around that. In this post I’m going to look at how Ruby also allows us to unwind a single block of code without raising an exception. I’ve said before that exceptions should be exceptional – the functionality in Ruby offers a lightweight version of this within a single scope.

Let’s see some code

This is much easier to explain with an example.

def get_number
  rand(100)
end

random_numbers = catch (:random_numbers) do
  result = []
  10.times do
    num = get_number
    throw :random_numbers if num < 10
    result << num
  end
  result
end

p random_numbers

The catch keyword defines a block with a given name. The block is processed normally until a throw statement is encountered.

When a throw statement is encountered Ruby will look up the call stack for a catch statement with the corresponding symbol. It will then unwind the call stack up to that point and terminate the block.

In this example the block will have two possible return values. If no number under 10 is generated the throw statement will never execute and the array of random numbers will be returned. If the throw statement does execute the block will simply return nil. Ruby also allows us to override this behavior – for example, instead of returning nil I might choose to return an empty array. We can do this by specifying a second parameter to the throw statement.

random_numbers = catch (:random_numbers) do
  result = []
  10.times do
    num = get_number
    throw(:random_numbers, []) if num < 10
    result << num
  end
  result
end

Performance

One of the reasons why it’s possibly not a good idea to overuse exceptions in your code is performance. Every time you raise an exception Ruby has to build a stack trace. If your exceptions are exceptional (as they should be) this won’t be a problem. However, if you’re using a large number of exceptions in a tight loop it could have a negative impact on performance. I’m assuming that a catch-throw block doesn’t need to create a stack trace – let’s see if we can get a rough idea of the performance impact.

start = Time.now
10_000_000.times do |i|
  begin
    raise StandardError, "Error #{i}"
  rescue StandardError => error
    error.inspect
  end
end
puts "Raise&Rescue Operation took #{Time.now - start} seconds"

start = Time.now
10_000_000.times do |i|
  catch (:the_loop) do
    throw :the_loop
  end
end
puts "Catch&Throw Operation took #{Time.now - start} seconds"

Performance

As I mentioned, these are very rough performance estimates, but there does seem to be a significant performance difference between using exceptions and using a catch-throw block.

Conclusion

The catch-throw block seems to be a lightweight error handling mechanism – useful when you want to jump out of a nested construct during normal processing. The performance figures point to the same conclusion.

Happy coding.

Понравилась статья? Поделить с друзьями:
  • Catch error jenkins
  • Catch error in vba
  • Catch error groovy
  • Catch error flutter
  • Catch error 404