RubyGuides
Share this post!

Everything You Need to Know About Ruby Operators

Ruby has a lot of interesting operators, like the spaceship operator (<=>), the modulo assignment operator (%=), and of course the regular operators like greater 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.

Logical Operators

The first group of operators we are going to look at are logical operators.

You 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.

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 request.

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 those “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

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.

Ternary Operator

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

It’s a way to write an if/else statement with very little code.

The format is like this:

condition ? true : false

Here’s an example:

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

The Shovel 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

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.

Operator Precedence

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, but not all of them are.

Hope you found this useful & interesting!

Thanks for reading 🙂

Leave a Comment:

6 comments
Lucas Caton says last month

Hi there,

Ternary operator is inverted 🙂
It should be:

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

Reply
    Jesus Castello says last month

    Hi Lucas,
    you are correct! I fixed it now, thank you 🙂

    Reply
Nathan says 4 weeks ago

Great Article – thanks for the write-up.

FYI:
irb(main):001:0> “abc” !~ /[0-9]/
=> true

Reply
    Jesus Castello says 4 weeks ago

    Hi Nathan,
    thanks for that, I have updated the article 🙂

    Reply
Jim Haungs says 3 weeks ago

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.

Reply
    Jesus Castello says 3 weeks ago

    Thanks for your feedback 🙂

    Reply
Add Your Reply