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.nameRead 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.nameGroup 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}"
endExample
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?