---
title: Prefer Objects as Method Parameters, Not Class Names
teaser:
tags: web,ruby,good code
author: Joe Ferris
published_on: 2014-09-26
---

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:

```ruby
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.

```ruby
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.

## Runtime vs Static State

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:

```ruby
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.

## Runtime State in Rails Applications

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:

```ruby
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:

```ruby
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.

## What's next

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

[Ruby Science]: http://rubyscience.com
