
Sooner or later, we all run into so-called Ruby Gotchas — those small language details that hide from our sight for hours of hardcore debugging. Here is a list of popular Ruby Gotchas and curiosities that developers should be aware of.
Most Ruby on Rails beginners get excited by the framework and start crafting applications without any knowledge of the language. And that’s the magic of RoR.
At some point things start to get serious. Some take time and effort to explore dirty secrets of Ruby on Rails, while others gloss over and become senior developers with almost zero knowledge of the language.
Anyway, sooner or later, beginners or experienced programmers, we all run into so-called Ruby Gotchas — those small language subtleties that hide from our sight for hours of hardcore debugging (puts '1').
Here is a list of popular Ruby gotchas and curiosities that developers should be aware of. For each case, there’s an example of confusing and/or error-prone code.
They come together with good practices, that will prevent you from making simple (but difficult to find) mistakes and simplify your (and your code maintainer’s) life.
“and” is NOT the same as “&&”
Likewise: or is NOT the same as ||
surprise = true and false # => surprise is true
surprise = true && false # => surprise is false
Good practice
Use only && / || operators.
In detail
and / oroperators have lower precedence than&& / ||- and / or have lower precedence than
=assignment operator, while&& / ||are of higher precedence - and and or have the same precedence, while
&&has higher precedence than||
The first example becomes clearer when we add parentheses that illustrate how using and differs from &&:
(surprise = true) and false # => surprise is true
surprise = (true && false) # => surprise is false
Some say: use and / or for flow control and && / || for boolean operations. I will say: don’t use keyword versions (and / or / not) at all (and go with more verbose ifs and unlesses). Less ambiguity, less confusion, less bugs.
More: Difference between “or” and || in Ruby?
“eql?” is NOT the same as “==”
…and NOT the same as equal? or ===.
1 == 1.0 # => true
1.eql? 1.0 # => false
Good practice
Use only == operator.
In detail
==, ===, eql? and equal? are all different operators, meant for different usage in different situations. You should always use == operator for comparing things, unless you have some specific needs (like you really need to differ 1.0 from 1) or manually override one of the equality operators for whatever reason.
Yes, the eql? version may look smarter than plain old == comparison, but does it really do what you meant it to do, like, just compare some things?
More: What’s the difference between equal?, eql?, ===, and ==?
“super” is NOT the same as “super()”
class Foo
def show
puts 'Foo#show'
end
end
class Bar < Foo
def show(text)
super
puts text
end
end
Bar.new.show('test')
This gives us:
ArgumentError: wrong number of arguments (1 for 0)
from (irb):2:in `show'
from (irb):9:in `show'
from (irb):15
from /usr/bin/irb:12:in `<main>'
Good practise
This is one of the places where omitting the parentheses is not only a matter of taste (or conventions), but actually changes the program logic.
In detail
super(without parentheses) will call parent method with exactly the same arguments that were passed to the original method (sosuperinsideBar#showbecomessuper('test')here, causing an error, because parent method does not take any arguments).super()(with parentheses) will call parent method without any arguments, just as expected.
More: Super keyword in Ruby
Your exception must not be an Exception
class BangBang < Exception
end
begin
raise BangBang
rescue
puts 'Caught it!'
end
Beware: this code will not catch BangBang and the message 'Caught it!' will not be displayed!
The result will be our BangBang exception raised and displayed:
BangBang: BangBang
from (irb):5
from /usr/bin/irb:12:in `<main>'
Good practise
- When defining your own exception class, inherit from
StandardErroror any of its descendants (the more specific, the better). Never useExceptionfor the parent. - Never
rescue Exception. If you want to do some general rescue, leaverescuestatement empty (or userescue => eto access the error).
In detail
- When you leave
rescuestatement empty, it means it will catch exceptions that inherit fromStandardError, notException. - When you
rescue Exception(which you should not), you’ll catch errors you won’t be able to recover from (like out of memory error). Also, you’ll catch system signals like SIGTERM, and in effect you won’t be able to terminate your script using CTRL-C.
More: Why is it bad style to rescue Exception => e in Ruby?
“class Foo::Bar” is NOT the same as “module Foo; class Bar”
MY_SCOPE = 'Global'
module Foo
MY_SCOPE = 'Foo Module'
class Bar
def scope1
puts MY_SCOPE
end
end
end
class Foo::Bar
def scope2
puts MY_SCOPE
end
end
See how MY_SCOPE value differs because of how we defined module/class:
Foo::Bar.new.scope1 # => "Foo Module"
Foo::Bar.new.scope2 # => "Global"
Good practise
Always use longer, more verbose version with classes wrapped by modules:
module Foo
class Bar
end
end
In detail
modulekeyword (as well asclassanddef) will create new lexical scope for all the things you put inside. So, ourmodule Foocreates the scope'Foo'in which ourMY_SCOPEconstant with'Foo Module'value resides.- Inside this module, we declare
class Bar, which creates new lexical scope (named'Foo::Bar'), which has access to its parent scope ('Foo') and all constants declared in it. - However, when you declare Foo::Bar with this
::“shortcut”:class Foo::Bar, it creates another lexical scope, which is also named'Foo::Bar', but here, it has no parent, and thus, no access to things from'Foo'scope. - Therefore, inside
class Foo::Bar, we have only access toMY_SCOPEconstant declared at the beginning of the script (without any module) with value'Global'.
More: Ruby — Lexical scope vs Inheritance
Most “bang!” methods return nil when they do nothing
'foo'.upcase! # => "FOO"
'FOO'.upcase! # => nil
Good practice
Never depend on built-in bang! methods return value, e.g. in conditional statements or in control flow:
@name.upcase! and render :show
Above code can cause some unpredictable behaviour (or, to be more specific, very predictable failure when @name is already in uppercase). Also, it is another example why you should not use and / or for control-flow shortcuts. No trees will be cut if you add those two enters there:
@name.upcase!
render :show
“attribute=” accessor always returns passed value, regardless of method return value
class Foo
def self.bar=(value)
@foo = value
return 'OK'
end
end
Foo.bar = 3 # => 3
(Note that the assignment method bar= returns 3 even though we explicitly return 'OK' at the end of its body.)
Good practice
Never rely on anything that happens inside assignment method, eg. in conditional statements like this:
puts 'Assigned' if (Foo.bar = 3) == 'OK' # => nil
This will obviously not work.
More: ruby, define []= operator, why can’t control return value?
“private” will NOT make your “self.method” private
class Foo
private
def self.bar
puts 'Not-so-private class method called'
end
end
Foo.bar # => "Not-so-private class method called"
(Note that if the method were private, Foo.bar would raise NoMethodError.)
Good practice
In order to make your class method private, you have to use private_class_method :method_name or put your private class method inside class << self block:
class Foo
class << self
private
def bar
puts 'Class method called'
end
end
def self.baz
puts 'Another class method called'
end
private_class_method :baz
end
Foo.bar # => NoMethodError: private method `bar' called for Foo:Class
Foo.baz # => NoMethodError: private method `baz' called for Foo:Class
More: creating private class method
I ain’t afraid of no Ruby Gotchas
Ruby gotchas listed above may not look like major mistakes, and at first sight they may seem like a matter of aesthetics or conventions.
Trust me — if you don’t deal with them, they’ll give you headaches. The headaches will lead to a heartbreak. And if you fall out of love with Ruby, you’ll stay alone. Forever.
Autor: Karol Sarnacki, CTO @ El Passion