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
rescue. So let’s start unlock the door to Ruby with these keywords!
Begin + End
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
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 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
We’re always going to want to
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 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.
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.
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!
begin endblock can be used to execute a block of code in a specific context.
Rescuecan be used to handle errors, while
ensurewill 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
end, and how to use them in the context of a method.
- Curious how
ensureworks in the context of an application? Here’s a blog post that dives into exactly that!