Most Ruby on Rails beginners get excited by the framework and start crafting applications without any knowledge of the language. And there’s nothing wrong about it. At least, unless those beginners persist in such approach and become senior developers without any 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″) and then we go That was that??! Really, this time I promise, I WILL read that book with a hammer on the cover! Or, rather, we go shit! and fall asleep.
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.
There are also good practices that will simplify your (and your code’s maintainer’s) life if you just stick to them. If you do not believe they are really good practices, read further through the details with more verbose explanation on why the particular gotcha can cause bugs (mostly because it does something else than most people think).
and / or is NOT the same as
&& / ||
surprise = true and false # => surprise is true surprise = true && false # => surprise is false
&& / || operators.
and / oroperators have lower precedence than
&& / ||
and / orhave lower precedence than
=assignment operator, while
&& / ||are of higher precedence
orhave 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
unlesses). Less ambiguity, less confusion, less bugs.
eql? is NOT the same as
== (and NOT the same as
1 == 1.0 # => true 1.eql? 1.0 # => false
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) or manually override one of the equality operators for whatever reason.
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?
super is NOT the same as
class Foo def show puts 'Foo#show' end end class Bar < Foo def show(text) super puts text end end Bar.new.show('test') # ArgumentError: wrong number of arguments (1 for 0)
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.
super(without parentheses) will call parent method with exactly the same arguments that were passed to the original method (so
super('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
class MyException < Exception end begin raise MyException rescue puts 'Caught it!' end # MyException: MyException # from (irb):17 # from /Users/karol/.rbenv/versions/2.1.0/bin/irb:11:in `<main>'
(This code will not catch
MyException and the message
'Caught it!' will not be displayed.)
- When defining your own exception class, inherit from
StandardErroror any of its descendants (the more specific, the better). Never use
Exceptionfor the parent.
rescue Exception. If you want to do some general rescue, leave
rescuestatement empty (or use
rescue => eto access the error).
- When you leave
rescuestatement empty, it means it will catch exceptions that inherit from
- 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.
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 Foo::Bar.new.scope1 # => "Foo Module" Foo::Bar.new.scope2 # => "Global"
Always use longer, more verbose version with classes wrapped by modules:
module Foo class Bar end end
modulekeyword (as well as
def) will create new lexical scope for all the things you put inside. So, our
module Foocreates the scope
'Foo'in which our
'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
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
- Therefore, inside
class Foo::Bar, we have only access to
MY_SCOPEconstant declared at the beginning of the script (without any module) with value
bang! methods return
nil when they do nothing
'foo'.upcase! # => "FOO" 'FOO'.upcase! # => nil
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=(value) method 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
3 even though we explicitly
return 'OK' at the end of its body.)
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.
private will NOT make your
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
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
I ain’t afraid of no Ruby Gotchas
Ruby gotchas listed above may not look like a big deal, and at first sight they may seem they are simple matter of aesthetics or conventions.
Trust me – if you don’t deal with them, eventually they will lead to some nasty headaches during Ruby on Rails development. And will cause heartbreak. Because you’ll fall out of love with Ruby. And then you’ll stay alone. Forever.