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 isfalse
ornil
then assign100
to it, otherwise just return the value ofa
”
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.
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 🙂
Hi there,
Ternary operator is inverted 🙂
It should be:
Hi Lucas,
you are correct! I fixed it now, thank you 🙂
Great Article – thanks for the write-up.
FYI:
irb(main):001:0> “abc” !~ /[0-9]/
=> true
Hi Nathan,
thanks for that, I have updated the article 🙂
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.
Thanks for your feedback 🙂