One day, I got lost in the Rubocop documentation. I was struck by a realization: there are many Ruby features I didn't know existed because Rubocop tells us not to use them.
Today, I wanted to share with you the 9 discoveries that surprised me, which Rubocop recommends avoiding!
And / Or Operators (Style/AndOr)
Did you know it is possible to write:
ary = %w[hello world]
ary.size == 2 **and** ary.last == 'world'
=> true
So why use &&
and ||
when you can directly write and
and or
?
The answer is quite simple, it's a matter of precedence compared to other operators.
The precedence table provided by the Ruby documentation shows us that the or
and and
operators are among the last to be evaluated in a Ruby expression.
In practice, this means we can form quite strange expressions, as indicated in the RubyStyle guide:
true or true and false # => false (it's effectively (true or true) and false)
true || true && false # => true (it's effectively true || (true && false)
false or true and false # => false (it's effectively (false or true) and false)
false || true && false # => false (it's effectively false || (true && false))
And it is due to its rather misleading behavior that we favor using &&
and ||
.
Then Keyword (Style/MultilineIfThen)
There is a keyword then
, which is accepted by Rubocop for one-line if-elsif
blocks.
a = rand(10)
if a < 3 then "Low"
elsif a < 6 then "Average"
elsif a < 8 then "High"
end
It is an interesting feature! It somewhat resembles a case
block but allows chaining different conditions easily.
Rubocop's rule recognizes that the following syntax is bad practice:
a = rand(10)
if a < 3 then
"Low"
elsif a < 6 then
"Average"
elsif a < 8 then
"High"
end
Which makes sense since then
is completely unnecessary in this case.
BEGIN and END blocks (Style/BeginBlock)
This was also a real discovery. By using BEGIN
, we can execute code when our Ruby script starts. END
does the same, but when our script finishes:
# test.rb
BEGIN { pp 'Hello World!'}
END { pp 'Goodbye World!'}
pp "Running my script..."
---
$ ruby test.rb
"Hello World!"
"Running a script"
"Goodbye World!"
I admit that in my daily life, I don't really think about using this syntax. I imagine it can be useful when designing CLI applications. To prepare and clean up a program.
?c → 'c' (Style/CharacterLiteral)
This one is very short. When you type ?
followed by an ASCII character, it returns the ASCII character as a string:
$ irb
001> ?a
=> "a"
002> ?\t
=> "\t"
What's the point? I don't really see either.
In fact, this feature was implemented to allow obtaining the ASCII code of the character. Before Ruby 1.9, you could do:
001> ?a
=> 97
002> ?\t
=> 9
But since Ruby 1.9, it only returns the character. The ASCII code can still be found via the .ord
method.
A good anecdote to tell at meetups!
There are a lot of aliases for collection methods (Style/CollectionMethods)
We are used to using .map
, .select
, or .include?
when manipulating an Enumerable. But did you know there is an alias for these methods?
Here is the list of methods with an alias that should be used (according to Rubocop):
.collect => .map
.collect! => .map!
.collect_concat => .flat_map
.inject => .reduce
.detect => .find
.find_all => .select
.member? => .include?
.lenght => .size
This Cop exists for one reason only: to ensure the use of a single method name throughout the project. In reality, I understand that it is impractical to have a project containing both .collect
and .map
. It can require a mental effort to remember that they are the same thing.
We have another justification for this choice in the RubyStyle documentation:
“The rhyming methods are inherited from Smalltalk and are not common in other programming languages. The reason the use of select
is encouraged over find_all
is that it goes together nicely with reject
and its name is pretty self-explanatory.”
% behaves like sprintf (Style/FormatString)
I discovered that we can use %
like sprintf
:
001> sprintf("%5d", 10)
=> " 10"
002> "%5d" % 01
=> " 10"
Basically, it's a shortcut but does exactly the same thing as sprintf
.
We can pass arguments to it in the form of an Array if using multiple arguments during formatting:
001> "Name: %s, Age: %d" % ["V", 23]
=> "Name: V, Age: 23"
002> "Name: %{name}, Age: %{age}" % { name: "V", age: 23 }
=> "Name: V, Age: 23"
Rubocop's rule is also there to ensure the use of a single method between sprintf
, format
, and %
.
%w[] is not only a story of
In Ruby, we are fortunate to have %w[Hello World]
notation to create an array of strings. But did you know it is possible to use almost any non-alphanumeric character as a delimiter?
%w{Hello World}
%w/Hello World/
%w@Hello World@
%w-Hello World-
%w*Hello World*
%w&Hello World&
# <> behaves like [] or ()
%w<Hello World>
...
So why do we use []
? The answer is given in the RubyStyle doc:
We most often use []
because it aligns with the array syntax [0, 1, 2]
. It's as simple as that!
But there are a few small exceptions like %q
which uses only parentheses ()
. But also regexes that use %r{}
because parentheses ()
and square brackets []
are frequently used in regexes.
FlipFlop Operator (Lint/FlipFlop)
This is a rather interesting find! The FlipFlop Operator. Let's see an example right away, and I'll explain the details:
# test.rb
text = <<~EOF
random line 1
start
interesting line 1
interesting line 2
end
random line 2
EOF
text.each_line do |line|
puts line if (line.chomp == 'start'..line.chomp == 'end')
end
---
$ ruby test.rb
start
interesting line 1
interesting line 2
end
The FlipFlop operator works in two phases, "flip" and "flop".
The operator "flips" (becomes true) when the first condition is true, and "flops" (becomes false again) when the second condition is true. Between these two points, it remains true, even if the first condition becomes false.
Honestly, I quite like this syntax.
Rubocop advises against using the FlipFlop operator in Ruby because it complicates code readability, making its behavior opaque to those unfamiliar with the operator. It encourages the use of clearer and more explicit alternatives such as loops and conditions to improve code maintainability.
There are a lot of Perl-style global variables (Style/SpecificGlobalVars)
I'm not going to list them all, but Ruby exposes many so-called “Perl-style” variables. You can find a complete list here.
Basically, these are variables that start with $
and can be found in two forms: the short form, or the long form in English.
Here's a small example of variables I liked:
# Print file name
puts $0
puts $PROGAM_NAME
# Print required files
puts $:
puts $LOAD_PATH
# Print error backtrace and error information
begin
File.read('non_existant_file.txt')
rescue
puts $ERROR_POSITION
puts $@
puts $ERROR_INFO
puts $!
end
There are many others, most of them having their equivalent in short/long versions.
Conclusion
I am delighted to see the diversity and richness of Ruby's features we explored today, even if Rubocop sometimes advises us to avoid them.
This variety demonstrates the flexibility and power of the language, offering us multiple ways to write and structure our code. Discovering these less common aspects can inspire us to experiment further and deepen our understanding of Ruby.
I would be very curious to know what the latest Rubocop Cop that taught you about our favorite language.
Feel free to subscribe so you don't miss my next breakdown on Ruby/Rails topic.
Top comments (0)