The Builder Pattern is similar to the Factory Pattern in that it provides you with the object that you need, but instead of just providing the right type or Class that you need, the Builder Pattern offers more detailed configuration.

In Design Patterns in Ruby Russ Olsen uses an easy-to-understand example of a Computer object and how you could use a ComputerBuilder to configure the Computer with different components like a display, motherboard, memory and drives. The purpose of the builder is to delegate the creation tasks to a builder object and abstract the details of how to configure a Computer object. When you use a builder object you can just tell the builder what you need.

Rather than reproduce Olsen’s code I here I recommend you head over to the Design Pattern book website and view the code samples from Chapter 14. Instead, I’m thinking of an example from my cycling days and considering how Trek might implement their Project One website, which allows customers to configure their dream bike (I provide the link only as credit to Trek, it’s actually a brutally slow and buggy Flash site).

We are going to start with a CustomerOrder object (or what the Gang of Four would call “the Director”), and the final product we want will be the Bike. If we were just picking between a road bike, mountain bike and hybrid bike we could get away with a factory that could return the type of bike. However, because they’ve navigated to the road bike section of the site we know the type, but our Bike will have different options so we’re going to need some configuration. We’ll have options for size, wheels and a gruppo (which includes shifters and drivetrain items like derailluers, cranks and gears) but customers can also configure their drivetrain more specifically.

Rather than have our CustomerOrder object handle all of this configuration, we will delegate it to a RoadBikeBuilder object. We could initialize our order with the type of Builder we want:

order = CustomerOrder.new(RoadBikeBuilder)

and then the two classes could look like this.

class CustomerOrder
    def initialize(builder)
        @builder = builder
    end
end

class RoadBikeBuilder
    attr_reader :bike

    def initialize
        @bike = RoadBike.new
    end

    def size(size_in_cm)
        @bike.size = size_in_cm
    end

    def wheels(model)
        @bike.wheels = WheelFactory.new(model)
    end

    def gruppo(brand)
        @bike.gruppo = GruppoFactory.new(brand)
    end

    def cranks(size, chainring_size)
        @bike.gruppo.crank_length = size
        @bike.gruppo.chainrings(chainring_size)
    end

    def gears(gear_ratio)
        @bike.gruppo.cassette(gear_ratio)
    end
end

You can see that I’m using factories inside the builder to return a model of wheels and a brand of gruppo, which are really just types of those things.

Now when we want to set the configuration of the bike we can do it like this:

order.builder.size(52)
order.builder.wheels(:race_x_lite)
order.builder.gruppo(:sram_force)
order.builder.cranks(170,52)
order.builder.gears("11x26")

and when we’re done we can say:

order.bike = builder.bike

Using the builder keeps our CustomerOrder from having to worry about all of the messy configuration details of a bike- plus, if the customer decides they want a mountain bike instead we can just pass the order a new builder.

Checking Your Built Object & Magic Methods

I’m not going to dive into them here, but Olsen goes on to discuss how you can use your Builder to check your object and also how you can configure the method_missing in your Builder to allow for “magic methods” that parse incoming methods to see if it might be a possible configuration option. It’s not too difficult to consider how you might implement these two things, but I’m afraid I haven’t set my bike example up well enough to easily take that step… plus I think I hear some fireworks that need checking out.