Skip to content

Commit

Permalink
Introduce data builder class
Browse files Browse the repository at this point in the history
Why:
We would like to separate the responsibilities of the builder and the
formatter such that it is easier to maintain

this commit:
introduces a builder class which will build the data hash for the
financial datum model

Introduce data_builder base class
  • Loading branch information
johnschoeman committed Apr 7, 2019
1 parent 12c3d9f commit 4250ecd
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 86 deletions.
6 changes: 6 additions & 0 deletions app/models/csv_builder.rb
@@ -0,0 +1,6 @@
class CsvBuilder < DataBuilder
def build(csv_row, user_id)
datum_as_hash = csv_row.to_h
formatter.format(datum_as_hash, user_id)
end
end
11 changes: 9 additions & 2 deletions app/models/csv_importer.rb
@@ -1,10 +1,17 @@
require "csv"

class CsvImporter < FileImporter
attr_reader :builder

def initialize(filename, user_id)
super
@builder = CsvBuilder.new
end

def import
CSV.foreach(file_path, headers: true) do |row|
formatted_datum = formatter.build_csv(row, user_id)
datum = FinancialDatum.new(formatted_datum)
raw_data = builder.build(row, user_id)
datum = FinancialDatum.new(raw_data)
datum.save
end
end
Expand Down
11 changes: 11 additions & 0 deletions app/models/data_builder.rb
@@ -0,0 +1,11 @@
class DataBuilder
attr_reader :formatter

def initialize
@formatter = FinancialDataFormatter.new
end

def build
raise "Override this method"
end
end
3 changes: 1 addition & 2 deletions app/models/file_importer.rb
@@ -1,10 +1,9 @@
class FileImporter
attr_reader :file_path, :user_id, :formatter
attr_reader :file_path, :user_id

def initialize(file_path, user_id)
@file_path = file_path
@user_id = user_id
@formatter = FinancialDataFormatter.new
end

def import
Expand Down
22 changes: 4 additions & 18 deletions app/models/financial_data_formatter.rb
@@ -1,21 +1,7 @@
class FinancialDataFormatter
def build_csv(csv_row, user_id)
datum_as_hash = csv_row.to_h
format_base_hash(datum_as_hash, user_id)
end

def build_xlsx(xlsx_row, user_id)
cells = xlsx_row.cells.map(&:value)
headers = %w[month year income expenses net_worth]
datum_as_hash = headers.zip(cells).to_h
format_base_hash(datum_as_hash, user_id)
end

private

def format_base_hash(datum_as_hash, user_id)
datum_as_hash["user_id"] = user_id
datum_as_hash["month"] = FinancialDatum::MONTHS[datum_as_hash["month"].to_i]
datum_as_hash
def format(raw_data, user_id)
raw_data[:user_id] = user_id
raw_data[:month] = FinancialDatum::MONTHS[raw_data["month"].to_i]
raw_data.symbolize_keys!
end
end
8 changes: 8 additions & 0 deletions app/models/xlsx_builder.rb
@@ -0,0 +1,8 @@
class XlsxBuilder < DataBuilder
def build(xlsx_row, user_id)
cells = xlsx_row.cells.map(&:value)
headers = %w[month year income expenses net_worth]
datum_as_hash = headers.zip(cells).to_h
formatter.format(datum_as_hash, user_id)
end
end
11 changes: 9 additions & 2 deletions app/models/xlsx_importer.rb
@@ -1,13 +1,20 @@
class XlsxImporter < FileImporter
attr_reader :builder

def initialize(filename, user_id)
super
@builder = XlsxBuilder.new
end

def import
workbook = RubyXL::Parser.parse(file_path)
worksheet = workbook.worksheets.first
worksheet.each_with_index do |row, idx|
if idx == 0
next
end
formatted_datum = formatter.build_xlsx(row, user_id)
datum = FinancialDatum.new(formatted_datum)
raw_data = builder.build(row, user_id)
datum = FinancialDatum.new(raw_data)
datum.save
end
end
Expand Down
31 changes: 31 additions & 0 deletions spec/models/csv_builder_spec.rb
@@ -0,0 +1,31 @@
require "rails_helper"
require "csv"

RSpec.describe CsvBuilder do
describe "#build" do
it "returns a hash of the data for a FinancialDatum" do
month = 1
year = 2019
expenses = 12345
income = 1000
net_worth = 999999
headers = %w[month year expenses income net_worth]
fields = [month, year, expenses, income, net_worth]
csv_row = CSV::Row.new(headers, fields)
user_id = 1
builder = CsvBuilder.new
expected_result = {
month: :february,
year: year,
expenses: expenses,
income: income,
net_worth: net_worth,
user_id: user_id,
}

result = builder.build(csv_row, user_id)

expect(result).to eq(expected_result)
end
end
end
12 changes: 12 additions & 0 deletions spec/models/data_builder_spec.rb
@@ -0,0 +1,12 @@
require "rails_helper"
require "csv"

RSpec.describe DataBuilder do
describe "#build" do
it "raises if it is called" do
builder = DataBuilder.new

expect { builder.build }.to raise_error("Override this method")
end
end
end
9 changes: 5 additions & 4 deletions spec/models/file_importer_spec.rb
Expand Up @@ -2,13 +2,14 @@

RSpec.describe FileImporter do
describe ".new" do
it "initializes with a file path and user_id and a formatter" do
filepath = "filepath"
it "initializes with a file path and user_id and a builder" do
file_path = "filepath"
user_id = 1

importer = FileImporter.new(filepath, user_id)
importer = FileImporter.new(file_path, user_id)

expect(importer.formatter).to be_an_instance_of(FinancialDataFormatter)
expect(importer.file_path).to eq(file_path)
expect(importer.user_id).to eq(user_id)
end
end
end
69 changes: 11 additions & 58 deletions spec/models/financial_data_formatter_spec.rb
@@ -1,72 +1,25 @@
require "rails_helper"
require "csv"

RSpec.describe FinancialDataFormatter do
describe "#build_csv" do
it "returns a hash of the data for a FinancialDatum" do
month = 1
year = 2019
expenses = 12345
income = 1000
net_worth = 999999
headers = %w[month year expenses income net_worth]
fields = [month, year, expenses, income, net_worth]
csv_row = CSV::Row.new(headers, fields)
user_id = 1
formatter = FinancialDataFormatter.new
expected_result = {
month: :february,
year: year,
expenses: expenses,
income: income,
net_worth: net_worth,
user_id: user_id,
describe "#format" do
it "takes a raw data hash and formats to the correct format" do
raw_data = {
month: 1, year: 2019, expenses: 10, income: 11, net_worth: 13,
}
.stringify_keys

result = formatter.build_csv(csv_row, user_id)

expect(result).to eq(expected_result)
end
end

describe "#build_xlsx" do
it "returns a hash of the data for a FinancialDatum" do
month = 1
year = 2019
expenses = 12345
income = 1000
net_worth = 999999
cell_one = build_cell(1, 0, month)
cell_two = build_cell(1, 1, year)
cell_three = build_cell(1, 2, income)
cell_four = build_cell(1, 3, expenses)
cell_five = build_cell(1, 4, net_worth)
xlsx_row = RubyXL::Row.new
xlsx_row.cells = [cell_one, cell_two, cell_three, cell_four, cell_five]
user_id = 1
formatter = FinancialDataFormatter.new
expected_result = {
month: :february,
year: year,
expenses: expenses,
income: income,
net_worth: net_worth,
user_id: user_id,
month: :january,
year: 2019,
expenses: 10,
income: 11,
net_worth: 13,
user_id: 1,
}
.stringify_keys

result = formatter.build_xlsx(xlsx_row, user_id)
result = formatter.format(raw_data, user_id)

expect(result).to eq(expected_result)
end
end

def build_cell(row, column, value)
cell = RubyXL::Cell.new
cell.row = row
cell.column = column
allow(cell).to receive(:value).and_return(value)
cell
end
end
42 changes: 42 additions & 0 deletions spec/models/xlsx_builder_spec.rb
@@ -0,0 +1,42 @@
require "rails_helper"

RSpec.describe XlsxBuilder do
describe "#build" do
it "returns a hash of the data for a FinancialDatum" do
month = 1
year = 2019
expenses = 12345
income = 1000
net_worth = 999999
cell_one = build_cell(1, 0, month)
cell_two = build_cell(1, 1, year)
cell_three = build_cell(1, 2, income)
cell_four = build_cell(1, 3, expenses)
cell_five = build_cell(1, 4, net_worth)
xlsx_row = RubyXL::Row.new
xlsx_row.cells = [cell_one, cell_two, cell_three, cell_four, cell_five]
user_id = 1
builder = XlsxBuilder.new
expected_result = {
month: :february,
year: year,
expenses: expenses,
income: income,
net_worth: net_worth,
user_id: user_id,
}

result = builder.build(xlsx_row, user_id)

expect(result).to eq(expected_result)
end
end

def build_cell(row, column, value)
cell = RubyXL::Cell.new
cell.row = row
cell.column = column
allow(cell).to receive(:value).and_return(value)
cell
end
end

0 comments on commit 4250ecd

Please sign in to comment.