---
title: Operador de Igualdade de Caso no Ruby
teaser: 'Uma introdução ao método `===` do Ruby, e como ele funciona em expressões
  `case` e além delas.

  '
tags: ruby,development
author: Neil Carvalho
published_on: 2022-12-19
---

Dê uma olhada em uma expressão `case` em Ruby:

```rb
case x
when 1, 2
  "É 1 ou 2"
when 3..10
  "É um número de 3 a 10"
when BigDecimal
  "É uma instância de BigDecimal"
when /bot/
  "É uma string que corresponde à expressão regular /bot/"
end
```

Comparadas a muitas linguagens que possuem _switch statements_, as expressões `case` do Ruby são muito mais flexíveis, correspondendo não apenas em casos de estrita igualdade, mas também em uma espécie de _filiação_ ou _correspondência_: `2` é um _membro_ do intervalo `1..3`; `5` é um _membro_ da classe `Integer`; `"thoughtbot"` _corresponde_ à expressão regular `/bot/`.

Este poder vem do operador `===` (ou operador de igualdade de caso, três iguais, igual triplo, como preferir). Cada argumento passado para `when` é comparado com `x` usando esse operador. O Ruby interpretará a expressão `case` acima como se fosse:

```rb
if 1 === x || 2 === x
  "É 1 ou 2"
elsif 3..10 === x
  "É um número de 3 a 10"
elsif BigDecimal === x
  "É uma instância de BigDecimal"
elsif /bot/ === x
  "É uma string que corresponde à expressão regular /bot/"
end
```

A classe `Object`, herdada pela maioria das classes Ruby, implementa o método `===` como um alias para `==`. A documentação para `Object` descreve-o como:

> Igualdade de caso – Para a classe `Object`, efetivamente o mesmo que chamar `#==`, mas normalmente sobrescrito por descendentes para fornecer semântica significativa em expressões `case`.

Como esses descendentes sobrescrevem o método `===`?`

## `Module`

A classe `Module`, herdada pela própria classe `Class`, implementa `===` como uma verificação `is_a?` no objeto fornecido. Ele retornará `true` se a classe ou módulo estiver na árvore ancestral do objeto fornecido.

```rb
Enumerable === [1, 2, 3]   # => true
Array === "A string"       # => false
String === "A string"      # => true
```

## `Range`

_Intervalos_ implementam `===` com o mesmo comportamento que `cover?`.

```rb
(1..10) === 3  # => true
(5..20) === 2  # => false
(5..20) === 15 # => true
```

<aside class="info">
Nota: esse comportamento mudou no Ruby 2.7. No Ruby 2.6 e abaixo, <code>===</code> se comporta como o método <code>include?</code>.
</aside>

```rb
(1..10) === 3.5 # => true
(5..20) === 2   # => false
```

## `Set`

Um _conjunto_ implementa `===` verificando se o objeto fornecido é um membro desse conjunto. Efetivamente, o mesmo que `include?`.

```rb
Set[1, 2, 3] === 2    # => true
Set[1, 2, 3] === "😀" # => false
```

## `Regexp`

Objetos `Regexp` retornarão `true` em `===` quando, dada uma string, a expressão regular corresponder à string. É próximo à implementação de `match?`.

```rb
/bot/ === "thoughtbot"                                 # => true
URI::MailTo::EMAIL_REGEXP === "https://thoughtbot.com" # => false
```

## `IPAddr`

Objetos `IPAddr` respondem a `===` como um equivalente a `include?`. Eles retornarão `true` se o IP fornecido for igual ou estiver dentro do intervalo.

```rb
subnet = IPAddr.new("192.168.2.0/24")
ip = IPAddr.new("192.168.2.100")

subnet.include?(ip) # => true
subnet === ip       # => true
```

## `Proc`

Objetos `Proc`, incluindo lambdas, têm maneiras demais de serem chamados, e `===` é mais uma delas.

```rb
Prime = ->(n) { n >= 2 && (2..Math.sqrt(n)).none? { |i| n % i == 0 } }

Prime === 3 # => true
Prime === 4 # => false # 4 não é um número primo
```

## Qualquer classe que você escrever

Agora que você sabe como as expressões `case` funcionam nos bastidores, você pode implementar o operador de igualdade de caso em suas próprias classes, fazendo correspondências de maneiras significativas, como por exemplo se um ponto GPS está dentro de uma área:

```rb
class Map::Area
  # ...

  def ===(other)
    if other.respond_to?(:latitude) && other.respond_to?(:longitude)
      # A coordenada fornecida está dentro desta área?
    else
      other == self
    end
  end
end

estado = Map::Area.new(lista_de_coordenadas_gps_para_desenhar_a_area)
cidade = Map::Point.new(-9.648139, -35.717239)

estado === cidade # => true
```

## Além das expressões `case`

Você pode estar se perguntando que, como as expressões `case` são um _code smell_ que seu código tem conhecimento demais, o método `===` não é tão útil. Mas há usos do operador de igualdade de caso além de seu propósito inicial.

O módulo `Enumerable` implementa muitos métodos que o usam. Um exemplo interessante é `#grep`. Ele retornará os elementos enumeráveis que correspondem ao padrão fornecido usando o método `===`, então:

```rb
[1, "a", 3].grep(Integer)       # => [1, 3]
["foo", "bar", "baz"].grep(/a/) # => ["bar", "baz"]
(1..10).grep(3..8)              # => [3, 4, 5, 6, 7, 8]
(1..15).grep(Prime)             # => [2, 3, 5, 7, 11, 13]
lugares.grep(area)              # => [local_1, local_3, ...]
```

Outros métodos de `Enumerable` que usam o operador de igualdade de caso são: `#all?`, `#none?`, `#one?`, `#any?`, `#grep_v`, `#slice_after` e `#slice_before`.
