๐ŸŽ“ A1Learner
๐Ÿ”
0 of 34 lessons completed

Ruby Project: Stock Report

This project builds a small stock report from a file. It brings classes, grouping and error handling together in one program.

A product that can be compared

The Product class includes Comparable and defines the spaceship method by price. The min call then picks the cheaper product.

class Product
  include Comparable
  attr_reader :name, :category, :price

  def initialize(name, category, price)
    @name = name
    @category = category
    @price = price
  end

  def <=>(other)
    price <=> other.price
  end
end
pear = Product.new "Pear", "Fruit", 5
apple = Product.new "Apple", "Fruit", 3
puts apple < pear
best = [pear, apple].min
puts best.name

Read lines into products

Each line of the stock file splits into three parts. The parts build one Product object per line.

class Product
  include Comparable
  attr_reader :name, :category, :price

  def initialize(name, category, price)
    @name = name
    @category = category
    @price = price
  end

  def <=>(other)
    price <=> other.price
  end
end
File.write "stock.txt", "Apple,Fruit,3\nPear,Fruit,5\n"
products = []
File.foreach "stock.txt" do |line|
  parts = line.chomp.split ","
  products << Product.new(parts[0], parts[1], parts[2].to_i)
end
puts products.length
puts products.first.name

Group and total

group_by sorts products into a hash keyed by category. reduce then adds the prices inside one group into a single total.

class Product
  include Comparable
  attr_reader :name, :category, :price

  def initialize(name, category, price)
    @name = name
    @category = category
    @price = price
  end

  def <=>(other)
    price <=> other.price
  end
end
apple = Product.new "Apple", "Fruit", 3
pear = Product.new "Pear", "Fruit", 5
bread = Product.new "Bread", "Bakery", 4
products = [apple, pear, bread]
groups = products.group_by { |product| product.category }
groups.each do |category, items|
  total = items.reduce(0) { |sum, product| sum + product.price }
  puts "#{category} total: #{total}"
end

Example

class BadLineError < StandardError
end

class Product
  include Comparable
  attr_reader :name, :category, :price

  def initialize(name, category, price)
    @name = name
    @category = category
    @price = price
  end

  def <=>(other)
    price <=> other.price
  end
end
File.write "stock.txt", "Apple,Fruit,3\nPear,Fruit,5\nBread,Bakery,4\nBadLine\nCake,Bakery,8\nMilk,Dairy,2\n"
products = []
File.foreach "stock.txt" do |line|
  begin
    parts = line.chomp.split ","
    raise BadLineError, "Bad stock line" if parts.length != 3
    products << Product.new(parts[0], parts[1], parts[2].to_i)
  rescue BadLineError => e
    puts e.message
  end
end
groups = products.group_by { |product| product.category }
groups.each do |category, items|
  total = items.reduce(0) { |sum, product| sum + product.price }
  puts "#{category} total: #{total}"
end
puts "Products: #{products.length}"
cheapest = products.min
puts "Cheapest: #{cheapest.name} at #{cheapest.price}"

Key points

  • The program writes a stock file before it reads it.
  • Each good line becomes a Product object.
  • A custom error class guards the shape of each line.
  • group_by sorts the products by category.
  • reduce adds each group into one total.
  • Comparable orders the products by price.

In this example, the program writes a stock file and reads it back line by line. Each good line becomes a Product, and the one bad line raises BadLineError and prints its message from the rescue. The report then totals each category group and names the cheapest product.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Which call groups the products by category?