Ruby Invoice

September 2017 · 10 minute read

I have recently challenged my skills building a small program to process an order and generate an itemized invoice which calculates the optimum pack sizes to make up a given quantity for the best price.

The completed project can be viewed at

Project Description

A fresh food supplier sells product items to customers in packs.  The bigger the pack, the cheaper the cost per item.

  • The supplier currently sells the following products
Product            	Packs          
Watermelons        	3 pack @ $6.99
                   	5 pack @ $8.99
Pineapples         	2 pack @ $9.95
                   	5 pack @ $16.95
                   	8 pack @ $24.95
Rockmelons         	3 pack @ $5.95
                  	5 pack @ $9.95
                   	9 pack @ $16.99

  • Your task is to build a system that can take a customer order…

For example, something like:

10 Watermelons 14 Pineapples 13 Rockmelons

  • And generate an invoice for the order…

For example, something like:

10 Watermelons         $17.98
   - 2 x 5 pack @ $8.99
14 Pineapples          $54.80
   - 1 x 8 pack @ $24.95
   - 3 x 2 pack @ $9.95
13 Rockmelons          $25.85
   - 2 x 5 pack @ $9.95
   - 1 x 3 pack @ $5.95
TOTAL                  $98.63

  • Note that the system has determined the optimal packs to fill the order. You can assume that bigger packs will always have a cheaper cost per unit price.


The first step in any new project, of course is consider your requirements and make a plan. It’s likely the plan will change later, but it’s a start point.

Why did I choose ruby?

  • I’m most familiar to me so I will get the product out in a reasonable timeframe.

  • Just worked out a great way to practice TDD for ruby apps using Travis-CI.

  • I can see this product being object oriented therefore ruby seems like a reasonable fit.


I’m probably going to split the compnents up into individual objects as follows:

  • item(item_name, pack)

  • pack(qty, price)

    • child of item (subClass)
  • order(items = [], basket)

  • basket(current_order=nil)

  • order_line(qty, item)

    • calculate optimal packs required to make up the qty
    • calculate total price of packs per product
  • invoice(order)

    order.items.each do | item |
    	puts item.get_receipt_line 


This is my planned perception of how the program will work:

Would you like to LIST available products, SHOP, VIEW basket, EXIT without placing an order?


list_products & packs


place your order:

$_ 10 watermelons


10 Watermelons			$17.98
	- 2 x 5 pack @ $8.99
TOTAL                  	$17.98

Would you like to complete your order and checkout now?

$_ yes

> Your order has been placed, thank you. Goodbye!



> Your order has not been placed, are you sure you want to leave?

$_ yes

> Goodbye!


  • If someone orders a quantity of product which does not equal a quantity made up of packs, they will be charged for the the nearest quantity above what they have ordered.

ie 11 Watermelons = 2x 5 packs + 1x 3pack

Build Process


Set up Travis-CI and blank app to get started

1. Create items and packs

  • Item class

  • Pack class

  • Watermelon class

I think for scalability, Watermelon should be a subclass of Item - I’m not quite sure yet how this is going to work. For now, I will just focus on getting the base of the app working.

Each sub-item will have a range of Packs available with pre-determined item qty’s and prices.

2. Handle user inputs

  • Handle_input class

I put this in next because it makes me feel better to know what the end result will connect to. At this stage, the only thing being checked is that a command is valid.

3. Define orders

  • Basket class

Need an empty basket to put the orders into

  • Order class

To put the items into the basket

4. Define each line in the order

  • Order_line class

The quantity and name of the item requested to be ordered by customer input

5. Determine optimal packs for each order line

Given what I have so far, there is enough to make the SHOP and LIST actions work with my HandleInput class.

SHOP should allow user to input an order_line in the format “3 watermelon”

  • OrderLine needs to determine the optimal # of packs to fill the order:
packs = []
left_over_qty = order_qty
pack.each (starting from largest value) do |p|
	left_over_qty / p
	for each whole result, packs << p
	left_over_qty = remainder

6. Add orderline packs to Order

  • OrderLine needs to be added to the overall Order

Both Basket and Order probably aren’t required at this point

I’ll start by initialising the Order object in app entry and update it with every additional item added when ‘shopping’

7. Review and refactor

Got a bunch of code working and doing it’s job. Time for a tidy up!

  • Refactor the code and revise tests

Although there is no duplicate code, there is certainly room for improvements and methods can be broken down into smaller easier-to-work-with components

8. Define invoice

  • Invoice class

VIEW should output the itemised invoice of the full order

9. Define additional products

Create additional fruit items

  • Reorganise code and review tests again.

  • Refactor shop_menu into it’s own method

10. Define LIST action

LIST should output a list of available items with their pack size and quantity. I probably should have listed the options first, but I was on a roll with SHOP and now that I’ve gotten this far, it seems less relevant. Still it will tie the app nicely to be able to view all available products and pack sizes.

11. Break down HandleInput methods

The handle_input class is getting too heavy, especially considering the size of the app. I’ve just taken this opportunity to also move the shop methods out into their own Shop object

12. Review OrderLine

I’m still pretty unhappy with the OrderLine methods. They’re chunky and painful to look at, which leads me to believe there must be a better way.

It also looks like I’ve made a mistake in selecting optimal pack sizes. I focussed too much on trying to make up qty’s which would not be an exact product of pack sizes available and in the process overlooked searching for an exact match first.

What I actually should have done was check if an exact match is possible first.

if order_qty === any sum of pack sizes
	make up order with those
	(using lowest price combo)
	go get whole/left_over_pieces to make up closest qty packs
    if qty does not add up, ignore it. 

This is actually trickier than I initially thought, but the best way to approach it is to break it right down and play around with a simple array in a seperate window. Here’s the test I ended up writing to get the whole optimal selection part working.

class ArrayCheck
  attr_reader :pack_qtys

  def initialize
    @pack_qtys = [[2, 6], [5, 9], [8, 16]]

  def optimal(qty)
    # Get Exact matches
    exact_matches = []
    pack_qtys.sort { |a, b| b <=> a }.each do |p, v|
      next unless (qty / p) * p == qty
      exact_matches << [qty / p, p, v]
    # puts exact_matches

    # Check for the optimim price
    price_check = []
    exact_matches.each do |x, y, z|
      val = (x * z)

      price_check << [x, y, z, val]
    # puts price_check

    numbers = { |x| x[3] }.map

    # Get Part matches
    partial_matches = []
    pack_qtys.sort { |a, b| b <=> a }.each do |p, v|
      partial_matches << [qty / p, p, v]
    # puts partial_matches

    # Top up part matches
    exact_partial = []
    partial_matches.each do |x, y, z|
      val = qty - (x * y)

      pack_qtys.detect do |a|
        if a.include?(val)
          exact_partial << [x, y, z]
          exact_partial << [val / a[0], a[0], a[1]]
    # puts exact_partial

    # Check for the optimim partial price
    partial_price_check = []
    exact_partial.each do |x, y, z|
      val = (x * z)

      partial_price_check << [x, y, z, val]
    # puts partial_price_check

    partial_numbers = { |x| x[3] }.map

    partial_price_array = []
    partial_numbers.each do |f|
      partial_price_array << f
    # puts partial_price_array

    puts "Exact Match: #{exact_matches}"
    # Array for bext price line: 1x 9pk @16 = $16
    puts "Best Exact Price: #{numbers.min}"
    puts "Partial Match: #{exact_partial}"
    puts "Best Partial Price: #{partial_price_array}"

    calculate_best(numbers.min, partial_price_array)

  def calculate_best(exact, partial)
    sub_total = []
    partial.each do |_x, _y, _z, val|
      sub_total << val

    # Find total cost of sub_items
    line_total = sub_total.inject(:+)

    # Return array of the cheapest line
    puts "\nThis is it #{partial}" if [exact[3], line_total].min == line_total

    puts "\nThis is it #{exact}" if [exact[3], line_total].min == exact

array =


Transfer the test code into my project and wallah! The pack selections that are meant to be made on orders are now being made!!

It’s late now, so before I call it a day I’m just going to make sure my existing tests pass. Although this is now working, it’s certainly not finished. I’ll break down the code in cleaner methods and write the tests fro them tomorrow.

13. Optimise OrderLine

Same process. Now that the system is actually working the way it should, I can go through and do a clean up.

a) Ensure all items are returned in the same format

Optimal method responses as determined by calculate_best method - I want everything to be returned as an Enumerator.

b) Split optimal sections out into individual methods

This will thin down the optimal method and make it easier to see what’s going on. By adding doc blocks, I will also be able to see clearly any duplicate methods.

def exact_match(pack_qtys, exact_matches)
    pack_qtys.sort { |a, b| b <=> a }.each do |p, v|
      next unless (order_qty / p) * p == order_qty
      exact_matches << [order_qty / p, p, v]


def price_check(exact_matches, price_check)
    exact_matches.each do |x, y, z|
      val = (x * z)

      price_check << [x, y, z, val]


def partial_matches(pack_qtys, partial_matches)
    pack_qtys.sort { |a, b| b <=> a }.each do |p, v|
      partial_matches << [order_qty / p, p, v]


def exact_partial(pack_qtys, partial_matches, exact_partial)
    partial_matches.each do |x, y, z|
      val = order_qty - (x * y)
      puts "VAL: #{val}"
      pack_qtys.detect do |a|
        # To be optimised: a.include?(val) does not cooperate with Money
        if a[0] == val || a[0] * 3 == val
          exact_partial << [x, y, z]
          exact_partial << [val / a[0], a[0], a[1]]


def partial_price_check(exact_partial, partial_price_check)
    exact_partial.each do |x, y, z|
      val = (x * z)

      partial_price_check << [x, y, z, val]


def partial_price_array(partial_numbers, partial_price_array)
    partial_numbers.each do |f|
      partial_price_array << f


c) Review those methods

Look to see where duplicate code can be cut out and find methods which are not required.

There are two identical methods - I only need this code once:

  • price_check

  • partial_price_check

Additionally, two methods are called currently to return one object. The difference is one extra param - these can be combined into one method.

  • partial_matches

  • exact_partial

There are now two identical lines in the optimal method which means they can be abstracted out into their own method.

exact_match_prices = { |x| x[3] }.map

partial_numbers = { |x| x[3] }.map

And they will become:

  def match_prices(array) { |x| x[3] }.map

I’ve also got a completely unnecessary method, since I’ll be handling Enumerators instead of Arrays, I won’t need this:

  • partial_price_array

d) Clean up calculate_best method

e) Optimal method check

  • Return unless there is an optimal match for packs (ignore the order item).

Documentation & Final Review

Of course, no good code is complete without doc blocks and no project is complete without a README. This is also a good chance to review and add additional tests which might have been overlooked. - Finished up with tests passing a 99% code coverage.