Bob Nadler, Jr. Bob Nadler, Jr.

Refactoring Case Statements in Ruby

Published almost 14 years ago 1 min read

case (or switch) statements in your code have a tendency to grow over time. After awhile they can become unmanageable and hard to read. As a general rule of thumb I try to refactor case statements if they are more than one screen long. But how can they be refactored? Well, there are several ways, but let's take a look at one in particular using Ruby.

Let's say you have a method that looks like this:

def output(data, format)
case format
when :html
return "<p>#{data}</p>"
when :text
return data
when :pdf
return "<pdf>#{data}</pdf>" # pseudocode -- obviously not valid PDF output
else
raise ArgumentError, "Invalid format (#{format})."
end
end

Admittedly, this is a short case statement, but it will do for illustrative purposes. Also, let's assume that the formatters are more complicated (obviously the PDF formatter will not work as is).

So the first step to refactoring this method is to move the formatters out of the output method. They can be actual classes, but for simplicity we'll convert them to lambdas.

HTMLFormatter = lambda { |data| "<p>#{data}</p>" }
TextFormatter = lambda { |data| data }
PDFFormatter = lambda { |data| "<pdf>#{data}</pdf" }
def output(data, format)
case format
when :html
return HTMLFormatter.call(data)
when :text
return TextFormatter.call(data)
when :pdf
return PDFFormatter.call(data)
else
raise ArgumentError, "Invalid format (#{format})."
end
end

OK, so we've moved the formatters out onto their own. Now for the case statement. First, we create a Hash using the format types as the keys and their lambdas as values. Second, we get rid of the case statement and instead look up the format type in the Hash and call it. Add in a line to check that the key exists, and we're done.

# Lambda's are used here for simplicity. In reality each formatter would
# probably be a separate class.
FORMATTERS = {
:html => lambda { |data| "<p>#{data}</p>" },
:text => lambda { |data| data },
:pdf => lambda { |data| "<pdf>#{data}</pdf>" }
}
def output(data, format)
raise ArgumentError,
"Invalid format (#{format})." unless FORMATTERS.has_key?(format)
FORMATTERS.fetch(format).call(data)
end

The same effect can be achieved by using classes instead of lambdas, just use the actual class instances as the values. Either way, the actual formatting is moved out of the output method into separate areas for each format type.


Share This Article