This post is part of a series of reviews on the book Design Patterns in Ruby. Check out the Introduction post for a full table of contents along with some generic principles regarding Design Patterns.

While the Factory Pattern is worried about which object to create, the Builder pattern is worried how to create it. Some objects require a load of work to be built and configured, and that’s what the Builder pattern eases out for us.

To illustrate the Builder pattern we’ll be using a simple Computer builder class:

class Computer
	attr_accessor :motherboard
	attr_accessor :display
	attr_accessor :drives

	def initialize(display=:crt, motherboard=Motherboard.new, drives=[])
		@motherboard = motherboard
		@drives = drives
		@display = display
	end
end

As you can see, the Computer object has a few pieces that need to be built, so let’s code them:

class CPU
	def process
		puts "I'm processing"
	end
end

class Motherboard
	attr_accessor :cpu
	attr_accessor :memory_size

	def initialize(cpu=CPU.new, memory_size=1000)
		@cpu = cpu
		@memory_size = memory_size
	end
end

class Drive
	attr_reader :type
	attr_reader :size
	attr_reader :writable

	def initialize(type, size, writable)
		@type = type
		@size = size
		@writable = writable
	end
end

As you can see, even with a simple layout like this one, creating a Computer object is still some cumbersome code:

motherboard = Motherboard.new(CPU.new, 2000)
drives = []
drives << Drive.new(:hard_drive, 200000, true)
drives << Drive.new(:cd, 760, true)
drives << Drive.new(:dvd, 4700, false)

computer = Computer.new(:lcd, motherboard, drives)
puts computer.inspect

To make this process simpler and more intuitive, we could use a builder:

class ComputerBuilder
	attr_reader :computer

	def initialize
		@computer = Computer.new
	end

	def display=(display)
		@computer.display = display
	end

	def memory_size=(size_in_mb)
		@computer.motherboard.memory_size = size_in_mb
	end
	
	def add_cd(writer=false)
		@computer.drives << Drive.new(:cd, 760, writer)
	end

	def add_dvd(writer=false)
		@computer.drives << Drive.new(:dvd, 4000, writer)
	end

	def add_hard_disk(size_in_mb)
		@computer.drives << Drive.new(:hard_disk, size_in_mb, true)
	end
end

Now all we need to do is create an instance of builder and call a few methods on it:

builder = ComputerBuilder.new
builder.add_cd(true)
builder.add_dvd
builder.add_hard_disk(10000)

puts builder.computer.inspect

In terms of lines of code we didn’t gain much (in this simple example), but in terms of code clarity we gained alot, and the fact that we could add much more complex creation code inside our builder and configure it with a single line of code is a big win.

Turns out we can also shorten the lines of code by using a technique called magic methods, which uses the same method_missing method we saw on the Proxy Pattern:

def method_missing(name, *args)
	words = name.to_s.split("_")
	return super(name, *args) unless words.shift == 'add'
	words.each do |word|
		next if word == 'and'
		add_cd if word == 'cd'
		add_dvd if word == 'dvd'
		add_hard_disk(100000) if word = 'harddisk'
	end	
end

By adding this to the ComputerBuilder method we can use methods such as this one:

builder = ComputerBuilder.new
builder.add_cd_and_dvd_and_harddisk

Whoever is familiar with Ruby on Rails should have noticed that this is also used by find methods such as find_by_name_and_email.