By Dmitry Pashkevich

No matter how many years of experience you have in software development, you can always benefit from learning a new technology as it broadens your horizons and teaches you new approaches to solving problems. Recently, I joined the team behind Calendly as a Full-Stack Engineer. Calendly’s back-end is built with Ruby on Rails, an ecosystem I had absolutely no prior experience in. Learning a language is not a big deal if you have an understanding of fundamental programming principles, but there are definitely things that take extra time to adapt to, depending on your background. I’ve been working closely with JavaScript/TypeScript for the past few years and thought I’d share my list of “gotchas” on my journey to mastering Ruby.

Getting over some low-level syntactic conventions like underscore_naming , the lack of semicolons and do..end blocks instead of curly braces is not a big deal, as you’ll just follow the conventions of the code base you work with. Instead of providing an exhaustive comparison of languages, I will highlight some idiomatic differences that may trip you up for weeks, or even months, into the new language.

Before we jump into the differences, it’s helpful to outline some similarities.

Both languages are general-purpose, interpreted and object-oriented. Like JavaScript, Ruby is dynamically-typed—there is no compiler to check data types before a program runs. The languages have similar built-in data types—for example, numbers, strings, arrays, hashes. They also both support closures; that is, scoped blocks or functions that remember the environment they were created in.

Now that we’ve established the common ground, let’s get to the interesting part. The sections below come in no particular order and are numbered merely for convenience.

1. Implicit returns

This one will be familiar to you if you’ve worked with languages like Scala, Lisp, Perl or Rust. In most other programming languages though, including JavaScript, you must be explicit about what you return from a function:

function add(a, b) { return a + b; }

In Ruby, however, the value of the last executed statement in a method is automatically returned. Here’s how the code above would be implemented in



Ruby:

def add(a, b) a + b end

The return keyword does exist in the language but is typically used for returning from a method early. You’ll get used to omitting the return keyword fairly quickly when writing your own code. The gotcha here is that when you read existing code, it’s not always obvious if a method is supposed to return something. Thus, it’s easy to accidentally add a log statement at the end of a method and not realize that you broke some code that depended on the return value.

def set_name(name) @name = name logger.log "Set name to #{name}" # you just altered the return value! end

2. Conditions at the end of statements

Ruby allows you to place conditions at the end of statements:

puts 'x is 42' if x == 42 puts 'x is NOT 42' unless x == 3

And yes, there’s also an unless keyword in Ruby. Such syntactic sugar makes the language more expressive, as in: it helps express the developer’s intentions in the code they write. That said, the trailing conditional syntax, when used incorrectly, can increase the cognitive overhead when reading the code.

3. Method calls without parentheses

In Ruby you can omit parentheses when calling a method. For example, the following two are equivalent:

Time.now() # returns current timestamp Time.now # also returns current timestamp

In JavaScript, you would expect the second line to return a reference to the now function, but in Ruby world that’s also a method call.

Let’s look at another example:

connection.post url, params, default_headers

Now you can tell that we’re invoking the post method here, but did you realize that url , params , default_headers can be method calls too? What if the params method happens to read from a database or make a network request? It’s easy to gloss over details like that and unintentionally introduce a performance regression to your code, especially if the “hidden” calls are happening in a loop.

Bonus fun fact: calling super and super() has different behavior.

4. Mutable strings

In Ruby strings are mutable. So you can do this:

s = 'mellow' s[0] = 'y' # change the first character puts s # prints 'yellow'

Mutability allows more efficient string operations that don’t involve cloning a string, but also means any method that accepts a string can potentially modify it. Good news: there’s a convention to append ! to a method name if it mutates the state of the object. Here’s an example from the standard library:

s.sub('foo', 'bar') # returns a copy of the original string with the replacement s.sub!('foo', 'bar') # performs the replacement in-place

Bad news: it’s just a convention and thus is not always followed outside of standard libraries. To prevent accidental modifications to a string (or any Object) you can freeze it by calling s.freeze .

By the way, String#replace in Ruby does a completely different thing from its JavaScript counterpart.

Note: Ruby 2.3 introduced string immutability that can be optionally enabled on a per file or per project basis. The feature is expected to be on by default on 3.0

5. Blocks, procs, lambdas

In the JavaScript world, it’s all about functions! They’re first-class citizens like other values, so they’re easy to assign, pass around and call at any given point. They’re a single way to describe a piece of code that needs to be executed later. Ok, technically function declarations and function expressions in JavaScript are not the same thing, but the differences are very subtle and the syntax for defining and using them is nearly identical. Enter Ruby, where you have four constructs that are kind of like JavaScript functions: methods, blocks, procs and lambdas. The last three are collectively called closures.

Ruby methods are not first-class citizens: they cannot be used as a value or passed around. They also don’t create a closure. We won’t get into the details as the usage of methods in Ruby is pretty straightforward and does not create confusing situations in my experience so far.

Blocks are like anonymous functions. Suppose we want to square all the elements in an array. Here’s how you would implement this in modern JavaScript:

[1, 2, 3].map(x => { return x * x; });

Here’s how it looks in Ruby:

[1, 2, 3].map do |x| x * x end

The piece of code between do..end is a block. Now what if we want to save our mapping function to a variable and pass it around? A pretty trivial task for JavaScript:

let squarer = x => { return x * x; } [1, 2, 3].map(squarer);

You would think that you could do something similar in Ruby:

# invalid syntax! squarer = do |x| x * x end [1, 2, 3].map(squarer)

But the above syntax is not valid. This was actually one of my first Ruby gotchas: blocks, like methods, are not first-class citizens. Ruby had to introduce a new type of object to get around the limitation: enter procs. Their sole purpose is to wrap a block so that it can be saved into a variable and passed around.

squarer = Proc.new do |x| x * x end [1, 2, 3].map(&squarer)

Note the & before squarer . Here we’re applying the unary & operator to the proc to convert it to a block (as if the block was inlined in the map call). To call a proc, you need to invoke its call method:

squarer.call(5) # returns 25

But wait, Ruby also has lambdas! Lambdas are very similar to procs, except that you use a lambda keyword to define it:

squarer_lambda = lambda do |x| x * x end

There are two ways lambdas are different from procs:

A lambda requires you to pass in the correct number of arguments upon calling it, while the proc does not. The line below will raise an error:

squarer_lambda.call # -> ArgumentError (wrong number of arguments (given 0, expected 1)) The return and break statements inside a lambda terminate that lambda (behavior equivalent to functions/function expressions in JS). But return and break inside a proc or a proc terminate the method that invoked them. This StackOverflow answer provides a more detailed explanation.

To recap:

A block is the most basic kind of closure in Ruby (think anonymous function expressions passed in-line)

If you need to assign a block to a variable in order to pass it around, you should wrap it into a proc

Use lambdas if you need to validate argument count or create a truly new execution context where return won’t exit from the calling method

While the fact that Ruby has four constructs that almost do the same thing is confusing to newcomers, you’ll become more comfortable with them as you get more exposed to the language’s ecosystem and common coding conventions. If you still feel confused, I recommend reading this article on Ruby’s closures.

6. Symbols

Symbols in Ruby are used for identifying things. What kinds of things? Hash keys, methods, classes, instance variables—whenever you reference these, you’re actually using symbols. You can also create your own symbols by prepending a name with a colon ( : ). Let’s look at an example:

status = :bad_request

Here we created a symbol :bad_request and assigned it to a variable. On the outside, symbols are similar to strings: both are objects that can hold characters of text, and the above line could’ve been written as

status = 'bad_request'

However, symbols have a few important differences:

Symbols are immutable. A symbol’s name never changes

A given symbol name refers to the same object throughout a Ruby program

This makes symbols more efficient than strings as identifiers. Every time 'bad_request' is found in the program, a new string would be created and later destroyed by the garbage collector. The same is not true for symbols. The first time the expression :bad_request is executed, a new symbol will get created, but subsequent occurrences will reference the same symbol object (Ruby keeps a running Symbol Table for the duration of the program), thus saving memory. Symbols are also fast to compare since the interpreter only needs to compare internal object IDs and not their names.

Symbols are commonly used as Hash keys in Ruby for these reasons. The following line creates a Hash literal with symbol keys and string values:

# keys are symbols user = {:first_name => 'Bruce', :last_name => 'Dickinson'}

Ruby 1.9 introduced an alternative, shorter syntax:

# keys are still symbols!!! user = {first_name: 'Bruce', last_name: 'Dickinson'}

Because the line above looks like valid JavaScript, it’s easy to get tricked into thinking that we’re creating a Hash object with strings for keys, but in fact we’re still using symbols! This syntax will use strings for keys:

# keys are strings user = {'first_name' => 'Bruce', 'last_name' => 'Dickinson'}

Likewise, pay attention to how you index into a Hash:

user[:first_name] # looks up by key :first_name (symbol) user['first_name'] # looks up by key 'first_name' (string) user.first_name # tries to call a method called first_name

This short article explains symbols in more detail.

Note: ES2015 introduced Symbols in JavaScript that are similar in concept, but are implemented differently. The practical application of Symbols in JavaScript is currently limited, although that may change in the future.

7. Equality operators

JavaScript has two equality comparison operators: == (loose equality) and === (strict equality). The former uses automatic type coercion, and the latter doesn’t:

// JavaScript1 == '1' // true0 == [] // true1 === '1' // false0 === [] // false

To make code behavior more predictable, it’s a common best practice in JavaScript to use === in most places. For the most part, Ruby doesn’t perform type coercion on comparison.

# Ruby1 == '1' # false0 == [] # false

Ruby does have a === operator, but its semantics are completely different. In fact, they depend on the implementation in the class of the object, because operators are just regular methods in Ruby. The triple equals operator is used under the hood in case statements to describe the matching logic in case statements. More on equality comparison operations here.

If you find yourself using === in Ruby, you probably meant == .

8. Strict truthiness

JavaScript has received a lot of criticism for its loose interpretation of what’s truthy and falsy. But after you get over the learning curve, you begin using that quirk of the language to write different kinds of shorthands. For example,

if (x)

if x is true

if x is not 0

if x is not an empty string

in JavaScript not only means, but alsoand

In Ruby, boolean evaluation of non-boolean values is strict: 0 , '' and [] are all evaluated to true . So time to unlearn some bad habits and be more explicit in your boolean expressions. The rule to remember is simple:

In Ruby only false and nil are falsy.

Use empty? to test objects for “emptiness.”

Conclusion

When you learn a new programming language, different syntax and new paradigms are always jarring in the beginning, but contrast is also what helps us learn. I encourage you to carry on along the learning curve and become a more well-rounded developer. Here’s a list of helpful introductory resources on Ruby for someone with experience in other programming languages:

What gotchas you up when you learned a new language? Please share your anecdotes in the comments!