Przesadziłabym mówiąc, że nie ma dnia, żebym pisząc w Rubym nie skorzystała z case’a. Wydawało mi się jednak, że znamy się już całkiem nieźle… aż pewnego razu okazało się, że właściwie „znamy sie tylko z widzenia”, bo nigdy nie zastanawiałam się nad tym jak działa pod maską.

Gdyby ktoś spytał mnie kiedyś jak należy rozumieć

case grade
when "A", "B"
  puts 'You’ve passed! Great!'
when "C", "D"
  puts 'You’ve passed… but...'
else
  puts 'You haven’t passed, sorry'
end

Odpowiedziałabym:

Jeżeli grade równa się A lub B - wpisz 'You’ve passed! Great!'. W przeciwnym razie, jeżeli jest równa C lub D - wypisz 'You’ve passed… but...'. W każdym innym przypadku wypisz 'You haven’t passed, sorry'.

Czy odpowiedziałabym poprawnie? Tak i nie.
Tak - ponieważ w tym przypadku do tego sprowadza się zapisana składnia i na tzw. „chłopski rozum” - tak właśnie zadziała.
Nie - ponieważ znaczyłoby to, że należałoby to przełożyć na zapis

if grade == "A" || grade == "B"
  puts 'You’ve passed! Great!'
elsif grade == "C" || grade == "D"
  puts 'You’ve passed… but...'
else
  puts 'You haven’t passed, sorry'
end

…a nie tak działa case!

Case equality, threequals

Mechanizm case opiera się o tzw. (zaskakująca nazwa) case-equality, czyli operator “===”, zwany również threequals Poprawny, przepisany zapis powyższego wywołania to

if "A" === grade || "B" === grade
  puts 'You’ve passed! Great!'
elsif "C" === grade || "D" === grade
  puts 'You’ve passed… but...'
else
  puts 'You haven’t passed, sorry'
end

Już na pierwszy rzut oka widać, że “wartości” znajdują się po przeciwnej stronie porównania niż zakladakałam… I kolejność ta, przy porównaniu za pomocą “===” ma znaczenie!
Jaka więc jest różnica pomiędzy “==” a “===” ?

== VS ===

W wielu przypadkach, threequals zachowa się tak jak “==”, porównując obiekty. Przy niektórych wywołaniach możemy jednak odczytać left_x === right_y jako pytanie _czy right_y można umieścić w pudełku left_x ?

Dlatego też, prawdę zwrócą wszystkie następujące wywołania:

Integer === 5             # zachowa się jak is_a?
(1..5) === 3              # include?
/test/ === 'myteststring' # =~, zwracając boolean

Fałszywe okażą się natomiast wszystkie te wywołania z zamienioną kolejnością argumentów. (Czy Integer można umieścić w pudełku 5?)

WARNING: instance class

Ciekawym przykładem, na którym łatwo jest się potknąć, jest sprawdzanie klasy danego obiektu.

obj = 'hello'
String === obj.class
#=> false
String === obj
#=> true

Dlaczego tak się dzieje? Ponieważ obj.class jest obiektem klasy Class nie String! Warto o tym pamiętać, gdyż jest to bardzo łatwy do popełnienia błąd, jeżeli używając case’a mamy z tyłu głowy „tradycyjne” porównanie - aż prosi się o użycie

def evaluate(obj)
  case obj.class
  when String
    obj.capitalize
  when Array
    obj.join.capitalize
  else
    'error'
  end
end

evaluate("my string")
#=> "error"
evaluate(["array", "of", "strings"])
#=> "error"

Poprawnym natomiast jest pominięcie wywołania metody .class na obiekcie

def evaluate(obj)
  case obj
  when String
    obj.capitalize
  when Array
    obj.join.capitalize
  else
    'error'
  end
end

evaluate("my string")
#=> "My string"
evaluate(["array", "of", "strings"])
#=> "Array of strings"

Podsumowanie

O czym warto w takim razie pomyśleć, pisząc kolejnego case'a?
Co w życiu zmieni nam === ?

Przede wszystkim należy pamiętać, że często, ale nie zawsze case equality zadziała jak ==.
Z drugiej strony, dzięki threequals mamy więcej możliwości niż przy tradycyjnym porównaniu wartości - możemy np. skorzystać z wyrażeń regularnych.

  case obj
  when /\d/
    "Object includes integers"
  when /^[a-zA-Z]*$/
    "Object contains only letters"
  else
    "Other"
  end
end