Words and Code

One writer’s journey from words to code.

Unlocking Ruby Keywords: Begin, End, Ensure, Rescue

I recently went down a little bit of rabbit hole in order to do some digging on database transactions. And when I say “a little bit”, I mostly mean “a lot bit”. I opened up the Rails source code for ActiveRecord::Transactions, and I saw some interesting terms I hadn’t seen in awhile.

Back when I was first learning Ruby a year ago, I remember reading about keywords. At the time, the only thing that really set them apart for me was the fact that they had special syntax highlighting in my text editor! (Yes, really.) But when I saw them again in the Rails source code, I thought it would be time to get a quick refresher on a few keywords that I saw again and again.

But first: what are keywords, exactly? Well, they’re definitely not objects, but instead are reserved words, which are actually defined in the Ruby parser. In fact, there’s a whole slew of keywords in Ruby, but the ones that I’m the most interested in sharing with you are begin, end, ensure, and rescue. So let’s start unlock the door to Ruby with these keywords!

Begin + End

The begin and end blocks are and interesting way of defining a bunch of code that needs to run on it’s own, in its own context. Between the begin and end keywords, we can put all the code that we need to run on its own. Just like with normal methods, whatever is the last expression that is evaluated in the begin end block is the result that will be returned.

1
2
3
4
5
def method_name
  begin
      # our method logic goes here
  end
end

Interestingly, the def method_name end keyword syntax of defining a method is also nothing more than a begin end block! I think that this RubyLearning tutorial explains this pretty well:

“It is to be noted that the body of a method definition is an implicit begin-end block; the begin is omitted, and the entire body of the method is subject to exception handling, ending with the end of the method.”

So, if the begin end syntax is just like writing a regular Ruby method, when would we ever use it? Well, it turns out that the begin end block can be helpful for defining chunks of code that need to execute in a certain order. For example, if we wanted to write a method that would split a group of Book objects into various batches, we might have something like this:

1
2
3
4
5
6
7
def create_or_update_batch
  if @batch.nil?
      @batch = BookBatch.create(book_batch.batch_attrs)
  end

  @batch.update
end

What we’re trying to do here is twofold: first, we want to calculate our BookBatch based on some attributes (batch_attrs) that we’re passing in, and then create it. And once we’ve done that, we want to update it.

We’re always going to want to create our BookBatch if it doesn’t already exist, definitely before we go about updating it. We can use the begin end syntax to do the same thing:

1
2
3
4
5
6
7
def create_or_update_batch
  @batch ||= begin
    BookBatch.create(book_batch.batch_attrs)
  end

  @batch.update
end

In this case, if a @batch instance doesn’t already exist, the code between the begin end will run and then the return value (a new BookBatch instance) will be assigned to the @batch instance variable. Only then will the @batch.update line be run.

It turns out that this kind of method architecture takes advantage of the Ruby interpreter’s stack. The ||= assignment that we are using before the begin end block just takes the last thing from the stack (in this case, BookBatch.create), and assigns it to @batch. Because the code in the begin end block runs independently in its own context, we can use it to contain and encapsulate a bit of logic. This is especially helpful if we want to rescue from an error!

Rescue

The rescue keyword is pretty cool, and is used by Rubyists quite a lot. It functions to handle exceptions, and takes a single argument: the class/type of error that you want to rescue from.

1
2
3
4
5
6
7
8
9
10
def create_or_update_batch
  @batch ||= begin
    BookBatch.create(book_batch.batch_attrs)
  end

  @batch.update

rescue
  raise_book_batch_error
end

One important thing about rescue: it’s always written at the same level as our method signature! In the example above, we have written our own raise_book_batch_error method, which will handle the exception once we’ve recused from this method. We can pass a specific type of error, or we can allow rescue to default. If you don’t pass any parameters to the rescue clause, it defaults to StandardError and will rescue any error by default.

Another important note about rescue is that is occurs at the same level as our method signature. So, if we want to rescue from our create_or_update_batch method, we need to have rescue on the same level as def create_or_update_batch. We could also rescue from inside of a begin end block:

1
2
3
4
5
6
7
8
9
10
11
12
def create_or_update_batch
  @batch ||= begin
    BookBatch.create(book_batch.batch_attrs)

  rescue ActiveRecord::AttributeAssignmentError
  end

  @batch.update

rescue
  raise_book_batch_error
end

In this case, the rescue clause inside of our begin end block will rescue any exceptions in its own context. It’s also important to note that only one rescue clause of a group will ever be executed.

Ensure

All of these blocks can start to be a little much after awhile. Sometimes, all you want to do is make sure that a specific piece of code will always run! Luckily, that’s what the ensure keyword is for. This keyword starts a section of code that is always run when an exception is raised. And just like rescue, it’s written at the same level as our method’s signature.

1
2
3
4
5
6
7
8
9
10
11
12
13
def create_or_update_batch
  @batch ||= begin
    BookBatch.create(book_batch.batch_attrs)
  end

  @batch.update

rescue
  raise_book_batch_error

ensure
  clean_up_orphaned_book_batches
end

In the example above, we’re ensuring that even if a BookBatch was created and didn’t get associated in the correct way and was orphaned, the clean_up_orphaned_book_batches method would always run. A good way to think about the ensure keyword is that its the last thing that will run in the method, and it will run no matter what, every single time.

The unique thing about ensure is that it runs even if there were any errors that were raised. The best practice is to usually put ensure at the end, so that the last part of the method will run.

The begin, end, rescue, and ensure methods are a little tricky to understand all together. But once we break them apart and figure out how they work on their own, things start to make a little more sense. Hopefully, diving into the rabbit hole of the Rails source code will be a little easier now. At the very least, we now have the keys to the wonderful world of Ruby keywords!

tl;dr?

  • The begin end block can be used to execute a block of code in a specific context. Rescue can be used to handle errors, while ensure will always run at the end of a method. The Ruby documentation has the complete list of Ruby keywords, and it’s clearly and thoroughly written. Check it out over here!
  • Check out this great blog post with lots of examples on begin and end, and how to use them in the context of a method.
  • Curious how ensure works in the context of an application? Here’s a blog post that dives into exactly that!