Ruby LanguageOperators

Remarks

Operators are methods

Most operators are actually just methods, so x + y is calling the + method of x with argument y, which would be written x.+(y). If you write a method of your own having semantic meaning of a given operator, you can implement your variant in the class.

As a silly example:

# A class that lets you operate on numbers by name.
class NamedInteger
  name_to_value = { 'one' => 1, 'two' => 2, ... }

  # define the plus method
  def + (left_addend, right_addend)
    name_to_value(left_addend) + name_to_value(right_addend)
  end

  ...
end

When to use && vs. and, || vs. or

Note that there are two ways to express booleans, either && or and, and || or or -- they are often interchangeable, but not always. We'll refer to these as "character" and "word" variants.

The character variants have higher precedence so reduce the need for parentheses in more complex statements helps avoid unexpected errors.

The word variants were originally intended as control flow operators rather than boolean operators. That is, they were designed to be used in chained method statements:

raise 'an error' and return

While they can be used as boolean operators, their lower precedence makes them unpredictable.

Secondly, many rubyists prefer the character variant when creating a boolean expression (one that evaluates to true or false) such as x.nil? || x.empty?. On the other hand, the word variants are preferred in cases where a series of methods are being evaluated, and one may fail. For example a common idiom using the word variant for methods that return nil on failure might look like:

def deliver_email
  # If the first fails, try the backup, and if that works, all good
  deliver_by_primary or deliver_by_backup and return
  # error handling code
end

Operator Precedence and Methods

From highest to lowest, this is the precedence table for Ruby. High precedence operations happen before low precedence operations.

╔═══════════════════════╦════════════════════════════════════════╦═════════╗
║ Operators             ║                 Operations             ║ Method? ║
╠═══════════════════════╬════════════════════════════════════════╬═════════╣
║ .                     ║ Method call (e.g. foo.bar)             ║         ║
║ []  []=               ║ Bracket Lookup, Bracket Set            ║    ✓¹   ║
║ ! ~ +                 ║ Boolean NOT, complement, unary plus    ║    ✓²   ║
║ **                    ║ Exponentiation                         ║    ✓    ║
║ -                     ║ Unary minus                            ║    ✓²   ║
║ * / %                 ║ Multiplication, division, modulo       ║    ✓    ║
║ + -                   ║ Addition, subtraction                  ║    ✓    ║
║ << >>                 ║ Bitwise shift                          ║    ✓    ║
║ &                     ║ Bitwise AND                            ║    ✓    ║
║ | ^                   ║ Bitwise OR, Bitwise XOR                ║    ✓    ║
║ < <= >= >             ║ Comparison                             ║    ✓    ║
║ <=> == != === =~ !~   ║ Equality, pattern matching, comparison ║    ✓³   ║
║ &&                    ║ Boolean AND                            ║         ║
║ ||                    ║ Boolean OR                             ║         ║
║ .. ...                ║ Inclusive range, Exclusive range       ║         ║
║ ? :                   ║ Ternary operator                       ║         ║
║ rescue                ║ Modifier rescue                        ║         ║
║ = += -=               ║ Assignments                            ║         ║
║ defined?              ║ Defined operator                       ║         ║
║ not                   ║ Boolean NOT                            ║         ║
║ or and                ║ Boolean OR, Boolean AND                ║         ║
║ if unless while until ║ Modifier if, unless, while, until      ║         ║
║ { }                   ║ Block with braces                      ║         ║
║ do end                ║ Block with do end                      ║         ║
╚═══════════════════════╩════════════════════════════════════════╩═════════╝

Unary + and unary - are for +obj, -obj or -(some_expression).

Modifier-if, modifier-unless, etc. are for the modifier versions of those keywords. For example, this is a modifier-unless expression:

a += 1 unless a.zero?

Operators with a ✓ may be defined as methods. Most methods are named exactly as the operator is named, for example:

class Foo
  def **(x)
    puts "Raising to the power of #{x}"
  end
  def <<(y)
    puts "Shifting left by #{y}"
  end
  def !
    puts "Boolean negation"
  end
end

Foo.new ** 2     #=> "Raising to the power of 2"
Foo.new << 3     #=> "Shifting left by 3"
!Foo.new         #=> "Boolean negation"

¹ The Bracket Lookup and Bracket Set methods ([] and []=) have their arguments defined after the name, for example:

class Foo
  def [](x)
    puts "Looking up item #{x}"
  end
  def []=(x,y)
    puts "Setting item #{x} to #{y}"
  end
end

f = Foo.new
f[:cats] = 42    #=> "Setting item cats to 42"
f[17]            #=> "Looking up item 17"

² The "unary plus" and "unary minus" operators are defined as methods named +@ and -@, for example

class Foo
  def -@
    puts "unary minus"
  end
  def +@
    puts "unary plus"
  end
end

f = Foo.new
+f               #=> "unary plus"
-f               #=> "unary minus"

³ In early versions of Ruby the inequality operator != and the non-matching operator !~ could not be defined as methods. Instead, the method for the corresponding equality operator == or matching operator =~ was invoked, and the result of that method was boolean inverted by Ruby.

If you do not define your own != or !~ operators the above behavior is still true. However, as of Ruby 1.9.1, those two operators may also be defined as methods:

class Foo
  def ==(x)
    puts "checking for EQUALITY with #{x}, returning false"
    false
  end
end

f = Foo.new
x = (f == 42)    #=> "checking for EQUALITY with 42, returning false"
puts x           #=> "false"
x = (f != 42)    #=> "checking for EQUALITY with 42, returning false"
puts x           #=> "true"

class Foo
  def !=(x)
    puts "Checking for INequality with #{x}"
  end
end

f != 42          #=> "checking for INequality with 42"

Case equality operator (===)

Also known as triple equals.

This operator does not test equality, but rather tests if the right operand has an IS A relationship with the left operand. As such, the popular name case equality operator is misleading.

This SO answer describes it thus: the best way to describe a === b is "if I have a drawer labeled a, does it make sense to put b in it?" In other words, does the set a include the member b?

Examples (source)

(1..5) === 3            # => true
(1..5) === 6            # => false

Integer === 42          # => true
Integer === 'fourtytwo' # => false

/ell/ === 'Hello'       # => true
/ell/ === 'Foobar'      # => false

Classes that override ===

Many classes override === to provide meaningful semantics in case statements. Some of them are:

╔═════════════════╦════════════════════╗
║      Class      ║     Synonym for    ║
╠═════════════════╬════════════════════╣
║ Array           ║ ==                 ║
║                 ║                    ║
║ Date            ║ ==                 ║
║                 ║                    ║
║ Module          ║ is_a?              ║
║                 ║                    ║
║ Object          ║ ==                 ║
║                 ║                    ║
║ Range           ║ include?           ║
║                 ║                    ║
║ Regexp          ║ =~                 ║
║                 ║                    ║
║ String          ║ ==                 ║
╚═════════════════╩════════════════════╝

Recommended practice

Explicit use of the case equality operator === should be avoided. It doesn't test equality but rather subsumption, and its use can be confusing. Code is clearer and easier to understand when the synonym method is used instead.

# Bad
Integer === 42
(1..5) === 3
/ell/ === 'Hello'

# Good, uses synonym method
42.is_a?(Integer)
(1..5).include?(3)
/ell/ =~ 'Hello'

Safe Navigation Operator

Ruby 2.3.0 added the safe navigation operator, &.. This operator is intended to shorten the paradigm of object && object.property && object.property.method in conditional statements.

For example, you have a House object with an address property, and you want to find the street_name from the address. To program this safely to avoid nil errors in older Ruby versions, you'd use code something like this:

if house && house.address && house.address.street_name
  house.address.street_name
end

The safe navigation operator shortens this condition. Instead, you can write:

if house&.address&.street_name
  house.address.street_name
end

Caution:
The safe navigation operator doesn't have exactly the same behavior as the chained conditional. Using the chained conditional (first example), the if block would not be executed if, say address was false. The safe navigation operator only recognises nil values, but permits values such as false. If address is false, using the SNO will yield an error:

house&.address&.street_name
# => undefined method `address' for false:FalseClass