Having written and read a lot of Ruby code, I occasionally come across some not-so-well-known features. Or even just a tiny detail that, as useless as it may be, I still find interesting.
In the past few days I finally got the chance (and the inspiration) to compile a list of those I found more useful.
1. attr_accessors are faster
We all know about
attr_reader|attr_writer|attr_accessor, which lets us write getter/setter methods easily for our instance variables. And we tend to think of them as just syntactic sugar. They certainly look better:
class WithDefaultAccessor attr_accessor :my_variable end class WithCustomAccessor def my_variable @my_variable end def my_variable=(value) @my_variable = value end end
But it turns out this also makes accessor methods more efficient. Here's my benchmark of both the reader and writer methods, with each benchmark calling the method 1 million times:
The speedup is around 50% when using
attr_accessor, which is a pretty big deal, especially for something such as instance variables, which are used virtually everywhere.
The explanation for this has to do with how Ruby handles method definitions internally. By using
attr_accessor, Ruby can flag the method to speed it up by directly calling a C function.
PS: This is not really useful advice, since you're probably already using this whenever possible. Still, it's nice to know Ruby is smart enough to optimize this for us.
2. Inline rescue
I've seen this pattern too many times:
Post.first.try(:title) || 'Default title'
and it gets even worse when we need to chain method calls to possibly-
Post.first.try(:author).try(:role).try(:name) || 'Default role name'
One way we could make this less ugly would be by using an inline
rescue, like so:
Post.first.author.role.name rescue 'Default role name'
If the code fails at any point, we just capture the exception and return a default value instead.
This raises a whole set of questions, especially about whether the
nil should be allowed to get there in the first place. That´s a different subject, worth its own share of discussion.
Still, I couldn't think of an actual example where this might make sense (perhaps there isn't one)
Because real programmers don't care about exceptions
In short, this allows you to re-run code after an exception was caught.
begin some_dangerous_stuff rescue SomeException => e retry end
Just like the previous one, the first time I saw this I immediately thought "Why would I ever use this?".
Turns out, a couple of days later, I was using cucumber to test an iOS app, which involved a not-so-reliable framework, and a few random timeout errors. Tests would sometimes fail due to reasons beyond our control, and would pass when run a second time. We ended up wrapping the test suite in this:
def insist(max_attempts: 3, &block) attempts = 0 begin yield rescue SomeException => e attempts += 1 retry if attempts < max_attempts end end
As strange as it may seem, this actually made our test suite green, so I officially like this feature now.
4. Lambdas quack like hashes
It's a common pattern to replace a function with a list of pre calculated values, when you decide to optimize it for speed. Take for instance the Fibonnaci sequence:
fibs = ->(x) do (x <= 1) ? 1 : fibs.call(x-1) + fibs.call(x-2) end
You might just need the first 10 fibonnaci numbers, so you just throw this in instead:
fibs = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
So it's nice to know that you can abstract this by using both of them the same way. Calling
fibs will work for both implementations. Lambdas respond to
, so these are exactly the same:
5. Easier RegEx extraction
When you just want to capture part of a string using regular expressions, you might do this:
str = "GB is not Great Britain" matches = str.match /(Great.*)/ matches => "Great Britain"
You can one-line this by simply doing:
str[/(Great.*)/, 1] => "Great Britain"
This is not rocket science, just something I rarely see people taking advantage of, so I guess it's not all that known.
6. Abstracting Array vs Element
If you use ActiveRecord, you might have stumbled onto this one already:
class Post < ActiveRecord::Base; end Post.find(1) => #<Post:...> Post.find([1, 2]) => #<Post:...>
What if we don't care wether we have a single id, or an array? Do we need a conditional to check the type returned by
An easier way to abstract this is:
results = Post.find(one_or_many_ids) [*results].each do |post| handle_a_single_post end
[*results] will wrap the argument in an array only if it is not already an array itself, so it will work for both cases.
The caveat here is that it doesn't work properly when the elements we're dealing with are arrays themselves.
PS: This can also be done via
Kernel.Array(results), or if you're using Ruby on Rails,
7. Unary operators
When you do
! is a unary operator, which you can actually override for your own classes. And the same goes for
We can use this to implement a small counter class as an example:
class Counter def initialize @value = 0 end def +@ @value += 1 end def -@ @value -= 1 end def ~@ @value end def !@ @value = 0 end end
And use it like this:
counter = Counter.new # 0 +counter # 1 +counter # 2 puts ~counter # outputs "2" !counter # resets to 0
This is certainly not how I would actually implement a counter, but there are probably better use cases for these operators.
8. Method class
For any Ruby object, you can get a reference to a particular method, for example:
That reference is actually a closure, that is bound to the object that called it. You can then call it, without having to explicitly provide the instance.
method = .method(:push) method.call(1) =>  method.call(2) => [1, 2]
Following up on the previous tip, you can also use
source_location on a method object to identify the file and line where it was defined.
# my_file.rb class CustomClass def a_method; end end # irb require 'my_file' method = CustomClass.new.method(:a_method) method.source_location => ["/Users/miguelpalhas/my_file.rb", 2]
This is particularly useful when, for example, you want to make sure your custom method is correctly overriding the one on the superclass/module.
I'm sure there are many more tricks such as these, probably enough for a whole new post some time in the future. Feel free to post your own in the comments below.