Ruby Science

Case Statement

Case Statements are a sign that a method contains too much knowledge.

Symptoms

  • Case statements that check the class of an object.
  • Case statements that check a type code.
  • Divergent change caused by changing or adding when clauses.
  • Shotgun surgery caused by duplicating the case statement.

Actual case statements are extremely easy to find. Just grep your codebase for “case.” However, you should also be on the lookout for case’s sinister cousin, the repetitive if-elsif.

Type Codes

Some applications contain type codes—fields that store type information about objects. These fields are easy to add and seem innocent, but result in code that’s harder to maintain. A better solution is to take advantage of Ruby’s ability to invoke different behavior based on an object’s class, called “dynamic dispatch.” Using a case statement with a type code inelegantly reproduces dynamic dispatch.

The special type column that ActiveRecord uses is not necessarily a type code. The type column is used to serialize an object’s class to the database so that the correct class can be instantiated later on. If you’re just using the type column to let ActiveRecord decide which class to instantiate, this isn’t a smell. However, make sure to avoid referencing the type column from case or if statements.

Example

This method summarizes the answers to a question. The summary varies based on the type of question.

# app/models/question.rb
def summary
  case question_type
  when 'MultipleChoice'
    summarize_multiple_choice_answers
  when 'Open'
    summarize_open_answers
  when 'Scale'
    summarize_scale_answers
  end
end

Note that many applications replicate the same case statement, which is a more serious offense. This view duplicates the case logic from Question#summary, this time in the form of multiple if statements:

# app/views/questions/_question.html.erb
<% if question.question_type == 'MultipleChoice' -%>
  <ol>
    <% question.options.each do |option| -%>
      <li>
        <%= submission_fields.radio_button , option.text, id: dom_id(option) %>
        <%= content_tag , option.text, for: dom_id(option) %>
      </li>
    <% end -%>
  </ol>
<% end -%>

<% if question.question_type == 'Scale' -%>
  <ol>
    <% question.steps.each do |step| -%>
      <li>
        <%= submission_fields.radio_button , step %>
        <%= submission_fields.label "text_#{step}", label: step %>
      </li>
    <% end -%>
  </ol>
<% end -%>

Solutions

Ruby Science

The canonical reference for writing fantastic Rails applications from authors who have created hundreds.

Work with us to make a new Rails app, or to maintain, improve, or scale your existing app.