Ruby Science

Use Class as Factory

An Abstract Factory is an object that knows how to build something, such as one of several possible strategies for summarizing answers to questions on a survey. An object that holds a reference to an abstract factory doesn’t need to know what class is going to be used; it trusts the factory to return an object that responds to the required interface.

Because classes are objects in Ruby, every class can act as an abstract factory. Using a class as a factory allows us to remove most explicit factory objects.

Uses

Example

This controller uses one of several possible summarizer strategies to generate a summary of answers to the questions on a survey:

# app/controllers/summaries_controller.rb
class SummariesController < ApplicationController
  def show
    @survey = Survey.find(params[])
    @summaries = @survey.summarize(summarizer)
  end

  private

  def summarizer
    case params[]
    when 'breakdown'
      Breakdown.new
    when 'most_recent'
      MostRecent.new
    when 'your_answers'
      UserAnswer.new(current_user)
    else
      raise "Unknown summary type: #{params[]}"
    end
  end
end

The summarizer method is a Factory Method. It returns a summarizer object based on params[:id].

We can refactor that using the abstract factory pattern:

def summarizer
  summarizer_factory.build
end

def summarizer_factory
  case params[]
  when 'breakdown'
    BreakdownFactory.new
  when 'most_recent'
    MostRecentFactory.new
  when 'your_answers'
    UserAnswerFactory.new(current_user)
  else
    raise "Unknown summary type: #{params[]}"
  end
end

Now the summarizer method asks the summarizer_factory method for an abstract factory, and it asks the factory to build the actual summarizer instance.

However, this means we need to provide an abstract factory for each summarizer strategy:

class BreakdownFactory
  def build
    Breakdown.new
  end
end

class MostRecentFactory
  def build
    MostRecent.new
  end
end
class UserAnswerFactory
  def initialize(user)
    @user = user
  end

  def build
    UserAnswer.new(@user)
  end
end

These factory classes are repetitive and don’t pull their weight. We can rip two of these classes out by using the actual summarizer class as the factory instance. First, let’s rename the build method to new, to follow the Ruby convention:

def summarizer
  summarizer_factory.new
end

class BreakdownFactory
  def new
    Breakdown.new
  end
end

class MostRecentFactory
  def new
    MostRecent.new
  end
end
class UserAnswerFactory
  def initialize(user)
    @user = user
  end

  def new
    UserAnswer.new(@user)
  end
end

Now, an instance of BreakdownFactory acts exactly like the Breakdown class itself, and the same is true of MostRecentFactory and MostRecent. Therefore, let’s use the classes themselves instead of instances of the factory classes:

def summarizer_factory
  case params[]
  when 'breakdown'
    Breakdown
  when 'most_recent'
    MostRecent
  when 'your_answers'
    UserAnswerFactory.new(current_user)
  else
    raise "Unknown summary type: #{params[]}"
  end
end

Now we can delete two of our factory classes.

Next Steps

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.