385

The best way I can describe what I'm looking for is to show you the failed code I've tried thus far:

case car
  when ['honda', 'acura'].include?(car)
    # code
  when 'toyota' || 'lexus'
    # code
end

I've got about 4 or 5 different when situations that should be triggered by approximately 50 different possible values of car. Is there a way to do this with case blocks or should I try a massive if block?

kenorb
  • 155,785
  • 88
  • 678
  • 743
Nick
  • 9,493
  • 8
  • 43
  • 66

6 Answers6

848

In a case statement, a , is the equivalent of || in an if statement.

case car
   when 'toyota', 'lexus'
      # code
end

Some other things you can do with a Ruby case statement

Charles Caldwell
  • 16,649
  • 4
  • 40
  • 47
  • I don't know why, but this strange situation happens: When I write this: `when "toyota", "lexus"`, I get: `unexpected tSTRING_BEG, expecting keyword_do or '{' or '(' (SyntaxError)`. However, when I write this: `when "toyota","lexus"`, it works. The only difference is a space after comma. – Furkan Ayhan Nov 10 '14 at 08:23
  • @FurkanAyhan That's odd. I went ahead and [tested the code](http://rubyfiddle.com/riddles/c6577) just to make sure and it does work. My guess is there's something else going on in your code that's making it error like that. Is it possible you forgot to close out a string somewhere or something like that? – Charles Caldwell Nov 10 '14 at 15:24
  • 1
    well, this works, but as ruby focus on programmer's ease, i wonder why it does nto support standard || or 'or'? This is kind of confusing – Zia Ul Rehman Mughal Mar 01 '17 at 12:33
  • @ZiaUlRehmanMughal, agreed. And even worse in my case, the || was causing a quiet error so it looked like the case succeeded until I really started digging in.. Beware. – JackChance Sep 05 '17 at 19:47
  • 3
    Ruby doesn't support `or` or `||` here because the `when` takes a series of comma-separated expressions to the right of it, not a single identifier. Because of this, if you had `when a or b`, it's not clear whether this is to be taken as the equivalent of `when a, b` or `when (a or b)`, the latter of which evaluates the expression `a or b` first before throwing it into the when. It's more surprising and less easy to handle for the language to have tokens that change behavior based on context, and then you wouldn't be able to use a real `or` expression on the right side of a when. – Taywee Aug 23 '18 at 19:46
  • @rsenna that link is broken – Alan Evangelista Mar 31 '21 at 08:33
120

You might take advantage of ruby's "splat" or flattening syntax.

This makes overgrown when clauses — you have about 10 values to test per branch if I understand correctly — a little more readable in my opinion. Additionally, you can modify the values to test at runtime. For example:

honda  = ['honda', 'acura', 'civic', 'element', 'fit', ...]
toyota = ['toyota', 'lexus', 'tercel', 'rx', 'yaris', ...]
...

if include_concept_cars
  honda += ['ev-ster', 'concept c', 'concept s', ...]
  ...
end

case car
when *toyota
  # Do something for Toyota cars
when *honda
  # Do something for Honda cars
...
end

Another common approach would be to use a hash as a dispatch table, with keys for each value of car and values that are some callable object encapsulating the code you wish to execute.

Nick
  • 9,493
  • 8
  • 43
  • 66
pilcrow
  • 56,591
  • 13
  • 94
  • 135
3

Remember switch/case (case/when, etc.) is just comparing values. I like the official answer in this instance for a simple or'd string list comparison, but for more exotic conditional / matching logic,

case true
  when ['honda', 'acura'].include?(car)
    # do something
  when (condition1 && (condition2 || condition3))
    # do  something different
  else
    # do something else
end
Ped
  • 39
  • 1
0

Another nice way to put your logic in data is something like this:

# Initialization.
CAR_TYPES = {
  foo_type: ['honda', 'acura', 'mercedes'],
  bar_type: ['toyota', 'lexus']
  # More...
}
@type_for_name = {}
CAR_TYPES.each { |type, names| names.each { |name| @type_for_name[type] = name } }

case @type_for_name[car]
when :foo_type
  # do foo things
when :bar_type
  # do bar things
end
Hew Wolff
  • 1,489
  • 8
  • 17
  • 1
    I don't mean to be rude, but I downvoted because this is less efficient in both time and space. It's also more complex and less readable than the other two answers. What would be the benefit of using this method? – Nick May 23 '19 at 19:03
  • It puts your whole classification into one object. You can now do things with that object, such as serialize it and send it to someone else to explain your logic, or store it in a database and allow people to edit it. (The logic will change pretty soon when new car models come out, right?) You might look up "table-driven". – Hew Wolff May 23 '19 at 21:57
  • 1
    YAGNI ("You aren't gonna need it") may apply here. The design sacrifices time/space efficiency and readability for a scenario that may exist in the future but doesn't exist yet. The cost is paid now, but the reward may never be reaped. – Nick May 28 '19 at 13:54
0

you could do something like this (inspired by @pilcrow's answer):

honda  = %w[honda acura civic element fit ...]
toyota = %w[toyota lexus tercel rx yaris ...]

honda += %w[ev_ster concept_c concept_s ...] if include_concept_cars

case car
when *toyota
  # Do something for Toyota cars
when *honda
  # Do something for Honda cars
...
end
Matheus Porto
  • 169
  • 1
  • 5
-3

In a case statement, equivalent of && in an if statement.

case coding_language when 'ror' && 'javascript' # code end