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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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, whileensure
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
andend
, 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!