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