I tried doing the coffee kata challenge the other day, https://simcap.github.io/coffeemachine/. I thought it would be quite fun to revisit my first language that got me my first job as a web developer, Ruby. I haven’t done Ruby and Rspec for so long. It wasn’t so hard to update the Ruby version in my machine and got going.
I decided to create an additional challenge by using statically-typed version of Ruby, using Airbnb’s Sorbet gem https://sorbet.org/.
Overall, the kata was quite challenging for me. Due to the way I structured my code. It was quite difficult to move beyond the third iteration of the kata :D. It’s a bit weird to be writing Ruby again using TDD. I don’t think I can get used to duck-typed language anymore. I have a better appreciation of Kotlin, Java and Typescript lately.
- Setting up Ruby and Rspec stack is quite verbose and not so straight-forward compared to other stacks. Too many files folders, in my opinion, to get started. I prefer to have only two files and go.
- Sorbet’s CLI tool is quite interesting – but since it cannot be run together with the TDD stack it can be a bit annoying. Also, there is no IDE plugin for Sorbet to help with writing the code.
I definitely suck at this kata. I need to revisit this soon with other stacks I’m more familiar with recently.
PS: Sorbet’s annotations are really whacky. It’s kinda interesting though.
Here is the repo to the solution:
https://github.com/suryast/coffee-kata-rb
# frozen_string_literal: true
# typed: strict
# define class CoffeeCart
# Implementation of CoffeeCart
class CoffeeCart
extend T::Sig
module PriceEnum
TEA = 0.4
COFFEE = 0.6
CHOCOLATE = 0.5
ORANGE_JUICE = 0.6
end
sig { params(drink: String, sugar: Integer, money: Float, extra_hot: T::Boolean).returns(String) }
def get_order(drink, sugar, money, extra_hot = false)
stick = sugar.positive? ? 0 : ''
sugar = sugar.zero? ? '' : sugar
extra_hot ? drink = (drink + 'h') : drink
missing_money = send_missing_amount(drink, money)
# return
correct_change?(drink, money) ? (drink + ':' + sugar.to_s + ':' + stick.to_s) : send_message(missing_money.to_s)
end
sig { params(_message: String).returns(String) }
def send_message(_message)
'M:' + _message.to_s
end
sig { params(drink: String, money: Float).returns(T::Boolean) }
def correct_change?(drink, money)
T.must(send_missing_amount(drink, money)).zero? ? true : false
end
sig { params(drink: String, money: Float).returns(T.nilable(T.any(Integer, Float))) }
def send_missing_amount(drink, money)
if !(drink == 'T' && money >= PriceEnum::TEA ||
drink == 'C' && money >= PriceEnum::COFFEE ||
drink == 'H' && money >= PriceEnum::CHOCOLATE ||
drink == 'O' && money >= PriceEnum::ORANGE_JUICE)
missing_amount(drink, money)
else
0.0
end
end
sig { params(drink: String, money: Float).returns(T.nilable(T.any(Integer, Float))) }
def missing_amount(drink, money)
if drink == 'T' || drink == 'Th'
(PriceEnum::TEA - money).round(1)
elsif drink == 'C' || drink == 'Ch'
(PriceEnum::COFFEE - money).round(1)
elsif drink == 'H' || drink = 'Hh'
(PriceEnum::CHOCOLATE - money).round(1)
else
(PriceEnum::ORANGE_JUICE - money).round(1)
end
end
private :correct_change?
private :send_missing_amount
private :missing_amount
end
# typed: false
# spec/test_spec.rb
require 'sorbet-runtime'
require 'rspec/sorbet'
require 'coffee_cart'
describe CoffeeCart do
describe '#receive_order' do
it 'should receive instruction to make drink with sugar' do
# GIVEN
drink = 'T'
sugar = 1
money = 0.4
# WHEN
result = CoffeeCart.new.get_order(drink, sugar, money)
# THEN
expect(result).to eq('T:1:0')
end
it 'should receive instruction to make drinks with no sugar' do
# GIVEN
drink = 'H'
sugar = 0
money = 0.5
# WHEN
result = CoffeeCart.new.get_order(drink, sugar, money)
# THEN
expect(result).to eq('H::')
end
it 'should receive instruction to make drinks with two sugar and add a stick' do
# GIVEN
drink = 'C'
sugar = 2
money = 0.6
# WHEN
result = CoffeeCart.new.get_order(drink, sugar, money)
# THEN
expect(result).to eq('C:2:0')
end
it 'should only make drink if the correct amount of money is given' do
# GIVEN
drink = 'T'
sugar = 1
money = 0.4
# WHEN
result = CoffeeCart.new.get_order(drink, sugar, money)
# THEN
expect(result).to eq('T:1:0')
end
it 'should return message with how much money missing for the order' do
# GIVEN
drink = 'T'
sugar = 1
money = 0.3
# WHEN
result = CoffeeCart.new.get_order(drink, sugar, money)
# THEN
expect(result).to eq('M:0.1')
end
it 'should make orange juice for 0.6 euro' do
# GIVEN
drink = 'O'
sugar = 0
money = 0.6
# WHEN
result = CoffeeCart.new.get_order(drink, sugar, money)
# THEN
expect(result).to eq('O::')
end
it 'should be extra hot drink when required' do
# GIVEN
drink = 'C'
sugar = 0
money = 0.6
extra_hot = true
# WHEN
result = CoffeeCart.new.get_order(drink, sugar, money, extra_hot)
# THEN
expect(result).to eq('Ch::')
end
end
describe '#deliver_message' do
it 'should receive a message with a drink order' do
# GIVEN
message = 'Hello'
# WHEN
result = CoffeeCart.new.send_message(message)
# THEN
expect(result).to eq('M:' + 'Hello')
end
end
end