Builder Design Pattern

Yair Fernando
Software Development with Ease
8 min readOct 20, 2020

Building maintainable, reusable, and comprehensive applications is a complicated task since you have to consider the scope of the project, how much can scale and how complex can become, so implementing best practices since the start of the project is crucial.

Design patterns are a way to go if you want to follow fundamental concepts for solving particular problems. In software development design patterns are used to solve specific design problems and help you to keep your code clean but not necessarily simple since usually and depending on the pattern that is being implemented your codebase can become quite complex. Using design patterns means that you are implementing basic concepts of OOP such as inheritance, polymorphism, encapsulation, abstraction and SOLID principles together with a set of guidelines that construct the given pattern.

There are three types of design patterns, creational design patterns, structural design patterns and behavioural design patterns, in this article, we’ll explain and build an application using the builder design pattern.

Builder design pattern
It is a creational pattern that lets you create complex objects following a series of steps. With this pattern, you can produce different types and representations of an object using the same construction code by executing a series of steps on what its called a builder object, each of these steps can have a different implementation based on the representation of the object that is being built, so this means that you can create several different builder classes that implement the same set of building steps but in a different manner. Also, this pattern does not allow other objects to access the object while is being built.

This is the basic concept of the builder pattern, but we still have to talk about the director-class and its benefits and different implementations.

The builder pattern can be applied when constructing different representations of the same object requires similar steps that only differ in the implementation, also when you want to build complex objects that require a lot of configuration steps.

With this pattern you can implement a director-class who is in charge of defining the order in which to execute the building steps and the implementation of those steps is provided by the builder object that is passed to the director class. It is also common to create an interface that declares all the steps that are required to be implemented in all types of builders.

Now that we know the theory of this pattern lets create an application and apply it.

Imagine that we have an application where we want to build three different types of sale reports, the simple sale report, detailed sale report and the full sale report. That sounds like a complex object that could be built with the same set of steps but with different implementations. We’ll use the builder pattern to accomplish this.

Let’s get started and create a new rails app.

rails new builder_pattern -d=postgresql

Create the DB

rails db:create

Now before we start working on the pattern we have to install a few gems and set up the environment.

First, let’s put these gems inside the Gemfile.

gem ‘pry-rails’
gem ‘devise’
gem ‘slim-rails’, ‘~> 3.2’

Run bundle install.

bundle install

Now since I’ll be using slim we have to convert the application.html.erb into an application.html.slim file. You can use this website to do it.

Now let’s install devise.

rails g devise:install

Add this line to the development.rb file.

config.action_mailer.default_url_options = { host: ‘localhost’, port: 3000 }

And the root to the index action of the home controller.

root to: “home#index”

Let’s generate the user model with devise.

rails g devise User

Add these two fields to the migration file

t.string :name, null: false, default: “”
t.integer :role, null: false, default: “”

Now migrate the DB.

rails db:migrate

Add this line to the application controller

before_action :authenticate_user!

We have to create the Home controller as well so let’s run

rails g controller Home index

Now let’s go to the console and create a new user

rails cUser.create(name: "john", email: "john@gmail.com", password: "password", password_confirmation: "password", role: "admin")

Great now we are done setting up devise, let’s start the server and go to /users/sign_in and log in with the user we just created. If everything went well we should see the index page of the home controller.

Now we can create the rest of the tables that we need.

rails g model CompanyType name code affiliation
rails g model Company business_name rfc email phone active:boolean contact fiscal_name user:references company_type:references
rails g model FiscalInformation ri account_statement proof_of_address incorporation_act start_of_operation:datetime company:references
rails g model Address street external_number country city state zip_code addressable_id:integer:index addressable_type:string:index
rails g model Client name email phone
rails g model Product name price:integer uid company:references
rails g model Sale seller_id:integer:index buyer_id:integer:index total:decimal status:integer sale_type company:references
rails g model SaleConcept unit_price total amount sale:references product:references
rails db:migrate

Now let’s add the associations

class Address < ApplicationRecord
belongs_to :addressable, polymorphic: true
end
class Client < ApplicationRecord
has_many :purchases, foreign_key: :buyer_id, class_name: "Sale"
has_one :address, as: :addressable
end
class CompanyType < ApplicationRecord
has_many :companies
end
class Company < ApplicationRecord
belongs_to :user
belongs_to :company_type
has_one :address, as: :addressable
has_many :products, dependent: :destroy
has_many :sales, foreign_key: :company_id, class_name: "Sale"
has_one :fiscal_info
delegate :name, :code, :affiliation, to: :company_type, prefix: "company_type", allow_nil: trueendclass Product < ApplicationRecord
belongs_to :company
has_many :purchase_concepts, foreign_key: :product_id, class_name: "PurchaseConcept"
has_many :purchases, through: :purchase_concepts, source: :purchase
endclass SaleConcept < ApplicationRecord
belongs_to :sale
belongs_to :product
end
class Sale < ApplicationRecord
enum status: [:completed, :rejected, :cancelled]
belongs_to :buyer, class_name: "Client"
belongs_to :seller, class_name: "User"
has_many :sale_concepts, dependent: :destroy
delegate :name, to: :seller, prefix: "seller", allow_nil: true
delegate :name, to: :buyer, prefix: "buyer", allow_nil: true
endclass User < ApplicationRecord
devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable
enum role: [:admin, :employee, :super_admin] has_many :companies, dependent: :destroy
has_one :address, as: :addressable, dependent: :destroy
has_many :sales, foreign_key: :seller_id, class_name: "Sale"
def active_companies
companies.where(active: true)
end
end

To put some dummy data into the DB, you can download this file from the repo and put it inside the root of your project, then run the following command.

psql builder_pattern_development < db.txt

Awesome, now we can start working on the builder pattern since we are going to generate different types of sales reports we should probably create a sales controller.

rails g controller Sales generate_report

Now inside the controller we well just call a service object and pass the params and the current user.

def generate_report
@report = SaleReportService.new(params, current_user).call
render json: @report
end

We have to create the service object, so within the app directory, create a services folder and a sale_report_service.rb file inside. In this service, we are going to have one method that will call the right method based on the params.

class SaleReportService
def initialize(params, user)
@params = params
@user = user
end
def call
case @params[:type]
when "simple" then get_simple_report
when "detailed" then get_detailed_report
when "full" then get_full_report
end
end
private def get_simple_report end def get_detailed_report end def get_full_report endend

Each of these methods will call the appropriate builder object to construct the right report.

Let’s create the builder objects that will construct each of these reports, within the app directory, create a builders folder. Now since in your applications you could have different types of builders like user builders or, product builders, we will create a sale folder to specify what type of builder we are implementing.

As I mentioned before in the builder pattern we have to create an interface that will declare the methods that all builder objects will implement. So, create a builder.rb file inside the sale folder.

class Sale::Builder

def add_user_info
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def add_company_info
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def add_company_fiscal_information
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def add_sales
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def add_sales_concepts
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def add_employee_info
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def add_client_info
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def add_charts
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end

This is the interface that every builder will implement to construct the object. In here we are declaring the methods that will be implemented and that make sense for the construction code of the sale report.

Next, we have to create the concrete builder subclasses that will create each type of report, and here it is when this pattern starts to grow and become a little bit more complicated since we are adding more complexity to the code by creating subclasses.

Let’s create the first concrete builder object simple_report_builder.rb that will implement the interface that we just created.

class Sale::SimpleReportBuilder < Sale::Builder

def initialize(user)
@user = user
@active_companies = @user.active_companies
reset
end
def reset
@report = Sale::SimpleReport.new
end
def report
report = @report
report
end
def add_user_info

end
def add_company_info end def add_company_fiscal_information end def add_sales end def add_sale_concepts end def add_employee_info end def add_client_info end def add_charts endend

In this first concrete builder subclass, we are receiving the user in the initialization method and we are setting two instance variables, @user and @active_companies, since we will need them in the construction code, we are also calling the reset method to get a new and clean builder object, which we also have to create simpel_report.rb.

class Sale::SimpleReport
attr_accessor :data
def initialize
@data = {}
end
end

This class only initialize the data as an empty object which the concrete builder will use to construct the complex object. So the concrete builder subclass will implement the right logic for this particular report and then will return the object when it is done by calling the report method.

The same thing will happen with other types of reports, we will have to create the concrete builder subclass that will implement the interface and return a different type of report and will also construct the complex object with its own logic.

As I mentioned we can also implement a director-class which will be in charge of calling the methods for the particular builder in the right order. So let’s create a director.rb class inside the sale folder.

class Sale::Director
attr_accessor :builder
def initialize
@builder = nil
end
def builder=(builder)
@builder = builder
end
def build
@builder.add_user_info
@builder.add_company_info
@builder.add_company_fiscal_information
@builder.add_sales
@builder.add_sale_concepts
@builder.add_employee_info
@builder.add_client_info
@builder.add_charts
end
end

The director initializes the builder to nil and has one method to set the builder and another one to build the final object by running each method in the right order.

Now we can go back to the sale_report_service.rb file and call the right builder object to construct the report.

def get_simple_report
@director = Sale::Director.new
builder = Sale::SimpleReportBuilder.new(@user)
@director.builder = builder
@director.build
builder.report.data
end
.....the same logic applies for the get_detailed_report and get_full_report, only change the concrete builder subclass.

In here we are first initializing the director, then initializing the concrete builder subclass and passing the user, then passing the builder object to the director class and then telling the director to build the object. Finally, we can access the report through the builder.

We have implemented the builder design pattern successfully if you want to check the concrete implementation of the methods for the construction of each report, feel free to clone the repo and check it out.

This is the final project companies_reports. email: carl@gmail.com, password: password.

Share this article if you liked it and thank you for reading.

--

--