In an application we worked on, we presented users with multiple choice questions and then displayed summaries of the answers. Users could see one of several summary types. You could view the percentage of users who selected the correct answer, or see a breakdown of the percentage of users who selected each answer.

Some of these summary classes were simple:

class MostRecentAnswer def summary_for ( question ) question . most_recent_answer_text end end

We allowed the user to select which summary to view, so we accepted a summary_type as a parameter. We needed to pass the summarizer around, so we accepted a class name in the parameters and that name directly to our model.

class SummariesController < ApplicationController def index @survey = Survey . find ( params [ :survey_id ]) @summaries = @survey . summaries_using ( params [ :summary_type ]) end end class Survey < ActiveRecord :: Base has_many :questions def summaries_using ( summarizer_type ) summarizer = summarizer_type . constantize . new questions . map do | question | summarizer . summary_for ( question ) end end end

This works, but it set us up for trouble later.

The Survey#summaries_using method accepts a class name, which means it can only reference constants instead of objects.

I’ve come to call this “class-oriented programming,” because it results in an over-emphasis on classes. Because code like this can only reference constants, it results in classes which use inheritance instead of composition.

Some Rails applications live with much of their data trapped in static state. Anything that isn’t a local or instance variable is static state. Here are some examples:

VERSION = 2 cattr_accessor :version self . version = 2 @@version = 2

We don’t usually talk about “static” methods and attributes in Ruby, but all of the information contained in the above example is static state, because only one reference can exist at one time for the entire program.

This becomes a problem when you want to mix static state and runtime state, because static state is viral, as static state can only compose other static state.

In our original example, you would be able to get away with using a class-based solution, because the MostRecentAnswer summarizer doesn’t need any information besides the question to summarize.

Here’s a new challenge: after the summary of each answer, also include the current user’s answer. Such a summarizer could be implemented in a decorator:

class WithUserAnswer def initialize ( base_summarizer , user ) @base_summarizer = base_summarizer @user = user end def summary_for ( question ) user_answer = question . answer_text_for ( @user ) base_summary = @base_summarizer . summary_for ( question ) " #{ base_summary } (Your answer: #{ user_answer } )" end end

This won’t work with a class-based solution, though, because the parameters to the initialize method vary for different summarizers. These parameters may have little in common and may be initialized far away from where they’re used, so it doesn’t make sense to pass all of them all of the time.

We can rewrite our example to pass an object instead of a class name:

class SummariesController < ApplicationController def index @survey = Survey . find ( params [ :survey_id ]) @summaries = @survey . summaries_using ( summarizer ) end private def summarizer if params [ :include_user_answer ] WithUserAnswer . new ( base_summarizer , current_user ) else base_summarizer end end def base_summarizer params [ :summary_type ]. constantize . new end end class Survey < ActiveRecord :: Base has_many :questions def summaries_using ( summarizer ) questions . map do | question | summarizer . summary_for ( question ) end end end

Now that Survey accepts a summarizer object instead of a class name, we can pass objects which combine static and runtime state, like the current user.

The controller still uses constantize , because it’s not possible to pass an object as an HTTP parameter. However, by avoiding class names as much as possible, this example has become more flexible.

You can learn more about factories, composition, decorators and more in Ruby Science.