Ruby Gotchas that will come back to haunt you

ruby on rails development

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 && / ||

Good practice

Use only && / || operators.

In detail

  • and / or operators 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 &&:

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 ===)

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()

Good practice

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 (so super inside Bar#show becomes 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 Exception

(This code will not catch MyException and the message 'Caught it!' will not be displayed.)

Good practice

  • When defining your own exception class, inherit from StandardError or any of its descendants (the more specific, the better). Never use Exception for the parent.
  • Never rescue Exception. If you want to do some general rescue, leave rescue statement empty (or use rescue => e to access the error).

In detail

  • When you leave rescue statement empty, it means it will catch exceptions that inherit from StandardError, not Exception.
  • 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

Good practice

Always use longer, more verbose version with classes wrapped by modules:

In detail

  • module keyword (as well as class and def) will create new lexical scope for all the things you put inside. So, our module Foo creates the scope 'Foo' in which our MY_SCOPE constant 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 to MY_SCOPE constant 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

Good practice

Never depend on built-in bang! methods return value, e.g. in conditional statements or in control flow:

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:

 


attribute=(value) method always returns passed value, regardless of method return value

(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:

This will obviously not work.

More: ruby, define []= operator, why can’t control return value?

 


private will NOT make your self.method private

(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:

More: creating private class method

 


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.

Ruby on Rails developer at EL Passion. Most of the time I code apps. Sometimes I code tunes. In both cases, results may vary. Get in touch with me.

  • Ben Sheldon

    I think your part on Exception is confusing or misleading. The big takeaways should be:

    1. Don’t rescue Exception (it’s perfectly fine to just rescue or rescue => e if you want to access the error). If you do rescue Exception, you’ll catch sigterms and out of memory errors and all sorts of stuff you don’t expect to be able to recover from.
    2. When defining your own exception class, don’t inherit from Exception, inherit from StandardException.

    • Karol Sarnacki

      Thanks! Indeed, this point should say “do not inherit from Exception” rather than “don’t leave rescue statement empty”. I reworded this point thanks to your suggestion.

  • Dave Aronson

    For more Ruby gotchas, see my presentation by that name. Slides available at:

    https://docs.google.com/presentation/d/1cqdp89_kolr4q1YAQaB-6i5GXip8MHyve8MvQ_1r6_s

  • Guest

    true and false
    #=> false

    • http://pikachuexe.weebly.com/ Leung Ho Kuen

      It works but with other operators in (like assignment) it is different from &&.
      But you can use it as a control flow stuff.
      e.g. record.save and do_something(record)

      I use and and or for raising errors

  • Robert Fletcher

    Wow, didn’t know about the attr= one. I had to try it out myself to believe that it was any different from a regular Ruby method.

  • Pingback: Ruby gotchas – Benjamin Oakes

  • Skofo

    I had no idea that class Foo::Bar differed from module Foo; class Bar. I am now using the latter in my gem project so I don’t have to include Foo everywhere, though I will miss the conciseness of the former.

  • coryschires

    I personally find ‘class << self' to be very annoying. It makes it more difficult to identify a class method at a glance. Also, it's annoying when trying to search a project for a given class method.

    • Karol Sarnacki

      Since Ruby 2.1, you can also do something like that:

      class Foo
      private_class_method def self.baz
      end
      end

      That is because now “def” method definition returns the method’s name symbol, allowing us to use this Java-like syntax for private/protected methods. This works for instance methods, too.

  • danielkummer

    Wow! Thanks for all the insight. I really feel enlightened right now… Especially the assign method and private methods part is quite surprising (and a little bit annoying)

  • kori@devmynd.com

    Can I also add:

    if false
    foo = “hello”
    end

    foo.class #=> NilClass

    The first time I saw this, really threw me for a loop.

    • patriot

      All local variables of the method before their first use have nil value (and variable.class #=> NilClass)

  • Pingback: Rails Development | Annotary

  • iwod

    Will Any of these “gotchas” be fixed? Improved? Corrected? minimized? Defined?

    Or are they considered part of Ruby?

  • Pingback: Ruby Gotchas that will come back to haunt you |...

  • Pingback: Ruby Gotchas that will come back to haunt you | Programming & Singularities

  • http://www.blazeboy.me BlazeBoy

    i tried to

    surprise = true and false

    and it returned false as expected

    • Marcin Kot

      Just check the value of “surprise” variable. Expression will return false because of “and false” part BUT true will be assigned to “surprise”.

      • http://www.blazeboy.me BlazeBoy

        oh, yes, that’s right

  • http://www.blazeboy.me BlazeBoy

    also tried
    (surprise = true) and false
    and returned false as expected