case

The case gem is a power-up for Ruby’s case-matching.

Structs

Case::Struct is a specialization of Ruby’s standard Struct class which supports structural matching. When two instances of a normal Ruby Struct are matched, their fields are compared using the normal equality test. Case::Struct uses the case-matching === operator over each field instead.

Here’s an example of some of the things which are possible:

require 'case'

Foo = Case::Struct.new :foo

[Foo["blah"], Foo["hoge"], Foo[3], Foo["testing"]].each do |value|
  case value
  when Foo["testing"]
    puts "matched testing"
  when Foo["blah"]
    puts "matched blah"
  when Foo[String]
    puts "matched string #{value.foo}"
  else
    puts "fell through"
  end
end

This will print:

matched blah
matched string hoge
fell through
matched testing

Wildcards

The case gem also supports wildcards. For example:

require 'case'

Foo = Case::Struct :name, :arg

[Foo["blah", 9], Foo["meat", 5], Foo["blah", 23]].each do |value|
  case value
  when Foo["blah", Case::Any]
    puts "blah: #{value.arg}"
  end
end

This will print:

blah: 9
blah: 23

Case::Any can also be used directly, though there’s seldom reason to do so instead of using an else clause.

case thing
when "foo"
  # specific case
when Case::Any
  # fall-through
end

case thing
when "foo"
  # specific case
else
  # fall-through
end

Conjunction

Case also supports simultaneously testing several cases in a single when:

[4.5, 3, 3.0].each do |value|
  case value
  when Case::All[Float, 3]
    puts "float 3"
  when 3
    puts "generic 3"
  when Float
    puts "generic float"
  end
end

This prints:

generic float
generic 3
float 3

Disjunction

It’s also possible to match one of several alternatives, if you pass arguments to Case::Any:

require 'rational'
require 'case'

[3, Rational(3, 1), 6.0, 3.0].each do |value|
  case value
  when Case::All[Case::Any[Float, Rational], 3]
    puts "matched #{value.inspect}"
  end
end

Which prints:

matched Rational(3, 1)
matched 3.0

Once again, Case::Any can be used on its own, though usually there’s not much point.

case thing
when Case::Any["foo", "bar"]
  # either of these
end

case thing
when "foo", "bar"
  # either of these
end

(Note that Case::Any[] with no arguments matches nothing; it is different from Case::Any without braces.)

Comparisons

Case::Cmp can be used to perform comparisons as part of case-matching:

require 'case'

(0..5).each do |value|
  case value
  when 0, 1
    puts "0 or 1"
  when Case::Cmp < 4
    puts "less than 4"
  else
    puts "fell through"
  end
end

This prints:

0 or 1
0 or 1
less than 4
less than 4
fell through
fell through

The <, >, >= and <= operators are all supported.

Guards

More general tests can be put in when statements using Case.guard.

require 'case'

["foobar", "beef", "lorem"].each do |value|
  case value
  when Case.guard { |v| v[1, 1] == "o" }
    puts "second letter in #{value} is o"
  else
    puts "just got #{value}"
  end
end

This will print:

second letter in foobar is o
just got beef
second letter in lorem is o

(Yes, I realize a regular expression could be used for this purpose.)

In simple cases, when Case.guard is being used directly, you don’t even need to bother with the block parameter:

["foobar", "beef", "lorem"].each do |value|
  case value
  when Case.guard { value[1, 1] == "o" }
    puts "second letter in #{value} is o"
  else
    puts "just got #{value}"
  end
end

(The parameter is really only there for when Case.guard is being used as part of a larger expression.)

Negation

Case::Not inverts the normal matching. For example:

[1, "foobar", :blah].each do |value|
  case value
  when Case::Not[String]
    puts value.inspect
  end
end

This will print:

1
:blah

Arrays

Case::Array is a subclass of Array which can be used as a pattern to do positional matching in arrays:

require 'case'

[["foo", 2], ["bar", 3, 4], ["bar", 9]].each do |value|
  case value
  when Case::Array["foo", Case::Any]
    puts "Foo #{value[1]}"
  when Case::Array["bar", Case::Any]
    puts "Bar #{value[1]}"
  end
end

This will print:

Foo 2
Bar 9

Note that Case::Array only matches arrays of the same length.

Case::Array should only be used as a pattern; there’s no need to use it in lieu of Array for your actual data.