The "begin" and "end" of Ruby

Aug 14, 2020

I recently went about cleaning up some of my earlier "syntax mistakes" in a Ruby project of mine. So what do I mean by syntax mistake? It is a term I use to define something that works perfectly fine, but leaves you with that feeling of "shouldn't there be a nicer way of writing this?". I think you know the feeling.

I believe this is quite common, with Ruby in particular, because there is seldom "one right way" of solving something. Instead they give you the tools to solve it however you personally see fit.

In this particular case, I'm going to show you a common pattern I used in my earlier Ruby days. It is regarding caching an instance method response.

def my_method
  return @my_method unless @my_method.nil?

  @my_method = SomethingElse.load
  @my_method.process_something
  @my_method.variable = 'assigned value'

  # ... etc.

  # Finally return the value that is now cached in an instance variable
  @my_method
end

Like I mentioned at the beginning, this works perfectly fine. But whenever I used it, I remember having that feeling of not being quite satisfied with it. An ugly return statement at the start of the method and a repetition of the variable at the end for the initial return value...

So what are other ways of writing the above code without changing the outcome?

Well, it depends on the scenario of course. The first I would say is to always use ||= whenever possible.

def my_method
  @my_method ||= SomethingElse.load
end

But that doesn't always work in itself. In the case of the first scenario, additional processing is required before you return, and what do you do then?

The second way could be to combine it with tap which allows you to work with the value before it is returned.

def my_method
  @my_method ||= SomethingElse.load.tap do |loaded|
    loaded.process_something
    loaded.variable = 'assigned value'
    # ... etc.
  end
end

But even that might not be sufficient sometimes. And what do you do then?

Ruby has two very important keywords, that should not be strange to anyone having worked in Ruby, begin and end. These are frequently used explicitly and even more frequently used implicitly (did you know that every method definition is processed inside a begin-end block? That is how you can use rescue inside the method without actually writing begin and end).

So a third way could be to use a begin-end block and cache the block response instead.

def my_method
  @my_method ||= begin
    loaded = SomethingElse.load
    loaded.process_something
    loaded.variable = 'assigned value'
    # ... etc.
    loaded
  end
end

Lastly, what if you want a way to circumvent the cached value? How about splitting them out into two separate methods

def my_method
  @my_method ||= my_method!
end

def my_method!
  loaded = SomethingElse.load
  loaded.process_something
  loaded.variable = 'assigned value'
  # ... etc.
  loaded
end

Are there other ways? Probably hundreds! :)

Happy coding!