Conditional code clutters methods, makes extraction and reuse harder
and can lead to leaky concerns. Object-oriented languages like Ruby
allow developers to avoid conditionals using polymorphism. Rather than
using if/else or
case/when to create a conditional path for
each possible situation, you can implement a method differently in
different classes, adding (or reusing) a class for each situation.
Replacing conditional code allows you to move decisions to the best
point in the application. Depending on polymorphic interfaces will
create classes that don’t need to change when the application
There are a number of issues with the summary
Adding a new question type will require modifying the method,
leading to divergent
The logic and data for summarizing every type of question and answer
is jammed into the Question class, resulting in a large class with obscure code.
This method isn’t the only place in the application where question
types are checked, meaning that new types will cause shotgun surgery.
There are several ways to refactor to use polymorphism. In this
chapter, we’ll demonstrate a solution that uses subclasses to replace
type codes, which is one of the simplest solutions to implement.
However, make sure to see the Drawbacks
section in this chapter for alternative implementations.
Let’s replace this case statement with polymorphism by introducing a
subclass for each type of question.
Our Question class is a subclass of
ActiveRecord::Base. If we want to create subclasses of
Question, we have to tell ActiveRecord which subclass to
instantiate when it fetches records from the questions
table. The mechanism Rails uses for storing instances of different
classes in the same table is called single
table inheritance. Rails will take care of most of the details, but
there are a few extra steps we need to take when refactoring to single
The first step to convert to STI
is generally to create a new subclass for each type. However, the
existing type codes are named “Open,” “Scale” and “MultipleChoice,”
which won’t make good class names. Names like “OpenQuestion” would be
better, so let’s start by changing the existing type codes:
The Question class stores its type code as
question_type. The Rails convention is to use a column
named type, but Rails will automatically start using STI if
that column is present. That means that renaming
question_type to type at this point would
result in debugging two things at once: possible breaks from renaming
and possible breaks from using STI. Therefore, let’s start by just
marking question_type as the inheritance column, allowing
us to debug STI failures by themselves:
Rails generates URLs and local variable names for partials based on
class names. Our views will now be getting instances of subclasses like
OpenQuestion rather than Question, so we’ll
need to update a few more references. For example, we’ll have to change
<%= form_for @question do |form| %>
<%= form_for @question, as: :question do |form| %>
Otherwise, it will generate /open_questions as a URL
instead of /questions. See commit
c18ebeb for the full change.
At this point, the tests are passing with STI in place, so we can
rename question_type to type, following the
# db/migrate/20121128225425_rename_question_type_to_type.rbclassRenameQuestionTypeToType<ActiveRecord::Migrationdef up rename_column :questions, :question_type, :typeenddef down rename_column :questions, :type, :question_typeendend
Now we need to build the appropriate subclass instead of
Question. We can use a little Ruby meta-programming to make
that fairly painless:
# app/controllers/questions_controller.rbdef build_question@question= type.constantize.new(question_params)@question.survey=@surveyenddef type params[:question][:type]end
At this point, we’re ready to proceed with a regular refactoring.
For each path of the condition, there is a sequence of steps.
The first step is to use extract method to move
each path to its own method. In this case, we already extracted methods
summarize_scale_answers, so we can proceed immediately.
The next step is to use move
method to move the extracted method to the appropriate class. First,
let’s move the method summarize_multiple_choice_answers to
MultipleChoiceQuestion and rename it to
The summary method is now much better. Adding new
question types is easier. The new subclass will implement
summary and the Question class doesn’t need to
change. The summary code for each type now lives with its type, so no
one class is cluttered up with the details.
Applications rarely check the type code in just one place. Running
grep on our example application reveals several more places. Most
interestingly, the views check the type before deciding how to render a
In the previous example, we moved type-specific code into
Question subclasses. However, moving view code would
violate MVC (introducing divergent change into
the subclasses) and, more importantly, it would be ugly and hard to
Rails has the ability to render views polymorphically. A line like
<%= render @question %>
—will ask @question which view should be rendered by
calling to_partial_path. As subclasses of
ActiveRecord::Base, our Question subclasses
will return a path based on their class name. This means that the above
line will attempt to render
open_questions/_open_question.html.erb for an open
question, and so on.
We can use this to move the type-specific view code into a view for
We already used views like
open_questions/_open_question.html.erb for showing a
question, so we can’t just put the edit code there. Rails doesn’t
support prefixes or suffixes in render, but we can do it
ourselves easily enough:
It’s worth noting that, although this refactoring improved this
particular example, replacing conditionals with polymorphism is not
without its drawbacks.
Using polymorphism like this makes it easier to add new types,
because adding a new type means that you just need to add a new class
and implement the required methods. Adding a new type won’t require
changes to any existing classes, and it’s easy to understand what the
types are because each type is encapsulated within a class.
However, this change makes it harder to add new behaviors. Adding a
new behavior will mean finding every type and adding a new method.
Understanding the behavior becomes more difficult because the
implementations are spread out among the types. Object-oriented
languages lean toward polymorphic implementations, but if you find
yourself adding behaviors much more often than adding types, you should
look into using observers or visitors instead.
Using subclasses forces you to use inheritance instead of composition
for reuse and separation of concerns. See composition
over inheritance for more on this subject.
Also, using STI has specific disadvantages. See the chapter
on STI for details.