Everything You Need to Know About Ruby Operators

Ruby has a lot of interesting operators.

Like:

  • The spaceship operator (<=>)
  • The modulo assignment operator (%=)
  • The triple equals (===) operator
  • Greater than (>) & less than (<)
  • Not equals (!=)

What you may not realize is that many of these operators are actually Ruby methods.

This means…

You can overwrite what they do & use them to define custom behavior in your own classes.

For example, by defining == you can tell Ruby how to compare two objects of the same class.

Now:

Let’s go over a few examples so you can get a solid overview of how these Ruby operators work & how to use them in your code.

This is an important topic if you really want to understand Ruby.

Contents

Ruby Logical Operators

First, we are going to look at logical operators.

You can use these operators to help you compare two objects & make a decision based on the result.

Here’s a table:

Operator Description
< Less than
> Greater than
>= Greater or equal than
<= Less or equal than
== Equals
!= Not equals
<=> Greater, Equal, Or Less

All these operators are methods, and they return a boolean value, with the exception of the spaceship operator. The spaceship operator returns either 1 (greater than), 0 (equal) or -1 (less than).

Here’s an example of how to use the > operator:

if orange.stock > 20
  # ...
end

If you want to use the double equals (==) operator with your own classes you may find that it doesn’t work at first…

Example:

class Fruit
 def initialize(name)
  @name = name
 end
end

orange1 = Fruit.new("orange")
orange2 = Fruit.new("orange")

orange1 == orange2
# false

The reason for this is that the default implementation of == is BasicObject#==, which uses the object_id method to find out if two objects are the same.

You can fix that like this:

class Fruit
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def ==(other)
    name == other.name
  end
end

Here we are saying what it means for two fruits to be the same:

They must have the same name.

Ruby Arithmetic Operators

The next set of operators are the arithmetic operators.

Nothing new here…

5 + 5
# 10

10 * 2
# 20

10 ** 2
# 100

But just like the == operator, these are methods.

This is useful because you can define what it means to add two objects together.

So if you have two Order objects, adding them together gives you the total amount to be paid, or you get a new order that is a combination of these two orders.

You can define exactly how you want that to work by defining the + method.

Another operator that you may not be familiar with is the modulo operator.

It looks like the percent sign (%).

And what it does is give you the remaining of a division.

Example:

10 % 2
# 0

The modulo operator has many practical uses, like finding whether a number is even or odd, if a number is divisible by another, for putting a limit on a number, etc.

Assignment Operators (==, +=, ||=)

Next up is assignment operators, and unlike all the operators we have seen until now, these are not methods.

You have the basic assignment operator:

a = 1

But you also have the combined assignment operators:

a += 5
# 6

a *= 2
# 12

These are equivalent to reading the current value & using one of the arithmetic operators with it, then saving the result. You can do this with all the arithmetic operators, including the modulo operator (%).

But there are two assignment operators that behave in a different way!

These are ||= and &&=.

They are different because they aren’t equivalent to the arithmetic versions.

What a ||= 100 does is this:

“If a doesn’t exist or if it is false or nil then assign 100 to it, otherwise just return the value of a

The closest I can get to an equivalent expression is this:

(defined?(a) && a) ? a : a = 100

This is useful if you want to save the result of some slow calculation or API request, a process known as “memoization”.

What Are Unary Operators?

Until now you have only seen operators that work with 2 values, but there are also operators that work with only one value, we call these “unary operators”.

For example:

+"abc"

This creates a mutable copy of a frozen string.

You can define your own unary operators (+ / -), but you’ll need some special syntax.

Example:

class String
  def +@
    frozen? ? self.dup : self
  end
end

str = "abc".freeze

p (+str).frozen?
# false

I have to use parenthesis here because of the operator precedence of unary operators.

You also have !!, which is not a method:

!!123
# true

!!nil
# false

This one is useful because it’ll turn any value into a boolean.

Then you have !, which is the same but it gives you the opposite boolean value.

Example:

!true
# false

!!true
# true

!false
# true

Ruby Splat Operator (With Examples)

The splat operator (*) is interesting because it does something you can’t do without it.

Let’s say you have an array like this:

attributes = [:title, :author, :category]

And you want to use this array with a method that takes variable arguments, like attr_reader.

Then you could do this:

attr_reader *attributes

The splat operator converts the array into a list of its elements. So it would be like taking away the array & replacing it with everything inside it.

In other words, the last example translates to:

attr_reader :title, :author, :category

That’s the power of the splat operator 🙂

Matching Operator (=~)

What is this funny-looking Ruby operator (=~) with a tilde?

It’s the matching operator!

It allows you to do a quick index search using a regular expression.

Here’s an example:

"3oranges" =~ /[0-9]/
# 0

This looks for numbers & returns the index inside the string where the first match is found, otherwise it returns nil.

In addition, you have the !~ operator, which is the “NOT match” operator.

Example:

"abc" !~ /[0-9]/
# false

You’ll get true or false with this, no indexes, so keep that in mind.

Ruby Ternary Operator (Question Mark Operator)

If you like compact & short code then you’re going to love the Ruby ternary operator.

It’s a way to write compact if/else statements.

It looks like this:

condition ? true : false

Here’s an example:

"".size == 0 ? "Empty string" : "Non-empty string"

This is how it works:

The first portion of a ternary operator defines the condition ("".size == 0).

Then you have a question mark symbol (?).

After that, you have the return value for when this condition is true.

Then a colon (:).

And the last part is the return value for when this condition is false, this would be the else in a full conditional expression.

The Shovel / Push Operator (<<)

This operator (<<) is also a method, so it changes what it does depending on what object you’re working with.

For example, with arrays it’s just an alias for the push method.

animals = []

animals << "cat"

With strings it will append to the end:

"" << "cat"

And with Integers, it will do a "left shift", which is rotating all the bits to the left.

2 << 1
# 4

2 << 2
# 8

2 << 3
# 16

Triple Equals Operator (More Than Equality)

Our last operator today is going to be about the triple equals operator (===).

This one is also a method, and it appears even in places where you wouldn't expect it to.

For example, in a case statement:

case "bacon"
when String
  puts "It's a string!"
when Integer
  puts "It's an integer"
end

Ruby is calling the === method here on the class.

Like this:

String === "bacon"

This compares the current class with the other object's class.

So the point of this operator is to define equality in the context of a case statement.

The Safe Navigator Operator (&.)

You may want to call a method on an object, but this object may be nil, which is no good because calling a method on nil often results in an error.

One solution:

if user && user.active
  # ...
end

A better way to do this:

if user&.active
  # ...
end

This &. is the safe navigator operator (introduced in Ruby 2.3), which only calls the active method on user if it's not nil.

Very useful!

Operator Precedence Table

Ruby evaluates your source code following a list of priorities, like what happens in math with multiplication & parenthesis.

This can become a source of all kind of errors if you don't understand how it works.

Here's is a table, from higher to lower precedence:

Operators
!, ~, unary +
**
unary -
*, /, %
+, -
<<, >>
&
|, ^
>, >=, <, <=
<=>, ==, ===, !=, =~, !~
&&
||
?, :
modifier-rescue
=, +=, -=, *=, /=, %=
defined?
not
or, and
modifier-if, modifier-unless, modifier-while, modifier-until
{ } blocks
do ... end blocks

With modifier-something it means the one-liner version of these keywords.

Example:

puts "awesome" if blog_name == "rubyguides"

Here's an example where the block precedence can surprise you:

# Returns array with uppercase characters
p ["a", "b", "c"].map { |character| character.upcase }

# Returns Enumerator object
p ["a", "b", "c"].map do |character|
  character.upcase
end

In the first case it works as expected, in the second case the block has lower precedence so map thinks there is no block & returns an enumerator.

Summary

You learned about Ruby's many operators, from the arithmetic operators, to logic & even the more obscure unary operators.

Many of these operators are actually methods that you can implement in your own classes.

Hope you found this useful & interesting!

Thanks for reading 🙂

6 thoughts on “Everything You Need to Know About Ruby Operators”

  1. Good article, but your explanation of this could be improved:

    p [“a”, “b”, “c”].map do |character|
    character.upcase
    end

    If you remove the p in front, you’ll see the do/end block behaves exactly the same as the {block} in braces, as it should; they are the same thing.

    Your explanation about the precedence is correct as far as it goes, but it could be enhanced by pointing out that, due to the lower precedence of do/end, the block is actually being associated with the p method call instead of with the map call. But p doesn’t take a block, so the do/end block is ignored, making the statement equivalent to:

    p [“a”, “b”, “c”].map

    If you substituted a different method for p that did take a one-arg block, it would execute the do/end block, but only once, unlike the map, which would be called three times.

Comments are closed.