Вот базовая c структура, которую вы можете использовать.
Меню
Сначала давайте создадим вспомогательный метод для отображения меню. Это ускоряет тестирование и упрощает изменение меню в будущем.
MENU = [{ label: "Add Item Charge", text: 'item', type: :item },
{ label: "Add Labor Charge", text: 'labor', type: :labor}]
def display_menu
puts "Menu ID \tMenu Item"
puts "-------\t\t----------"
MENU.each.with_index(1) { |h,i| puts "#{i}\t\t#{h[:label]}" }
puts "0\t\tExit application"
end
Давайте попробуем.
display_menu
Menu ID Menu Item
------- ----------
1 Add Item Charge
2 Add Labor Charge
0 Exit application
Обратите внимание, что для внесения изменений в пункты меню необходимо изменить только MENU
.
На данный момент не обращайте внимания на клавиши :text
и :type
в MENU
.
Добавление сборов
Если пользователь желает добавить плату за элемент, будет вызван следующий метод с аргументом, который является меткой для типа сбора и вернет действительную сумму в долларах:
def item_charge(charge_type)
loop do
print "Enter #{charge_type} amount: $"
case (amt = Float(gets, exception: false))
when nil
puts " Entry must be in dollars or dollars and cents"
when -Float::INFINITY...0
puts " Entry cannot be negative"
else
break amt
end
end
end
См. Kernel :: Float . Этот метод получил опцию :exception
в Ruby v2.7. Более ранние версии вызывали исключение, если строка не могла быть преобразована в число с плавающей точкой. Это исключение должно быть спасено и nil
возвращено.
Вот возможный диалог:
item_charge('labor')
Enter labor amount: $213.46x
Entry must be in dollars or dollars and cents
Enter labor amount: $-76
Entry cannot be negative
Enter labor amount: $154.73
#=> 154.73
Экономия расходов
Теперь давайте создадим га sh для хранения общей стоимости предметов и стоимость рабочей силы со значениями, инициализированными как nil
.
costs = MENU.each_with_object({}) { |g,h| h[g[:type]] = nil }
#=> {:item=>nil, :labor=>nil}
Это будет первая строка метода enter_costs
ниже.
OK для выхода?
Давайте предположим, что мы не хотим разрешать пользователю выходить из приложения до того, как он введет оплату труда и, по крайней мере, один пункт оплаты. Мы могли бы написать отдельный метод, чтобы проверить, отвечают ли они этому требованию:
def ok_to_exit?(costs)
h = MENU.find { |h| costs[h[:type]].nil? }
puts " You have not yet entered a #{h[:text]} cost" unless h.nil?
h.nil?
end
См. Перечислимый # find .
Допустим ли выбор меню?
Нам нужно проверить, является ли числовое значение, введенное для выбора идентификатора меню (отличным от нуля, который мы уже проверили):
def menu_selection_valid?(menu_id)
valid = (1..MENU.size).cover?(menu_id)
puts " That is not a valid menu id" unless valid
valid
end
menu_selection_valid?(4)
That is not a valid menu id
#=> false
menu_selection_valid?(-3)
That is not a valid menu id
#=> false
menu_selection_valid?(2)
#=> true
Кузов
Теперь мы можем собрать все вместе.
def enter_costs
costs = MENU.each_with_object({}) { |g,h| h[g[:type]] = nil }
loop do
display_menu
print ("Enter Menu ID: ")
menu_id = gets().to_i
if menu_id.zero?
ok_to_exit?(costs) ? break : next
end
next unless menu_selection_valid?(menu_id)
h = MENU[menu_id-1]
case h[:type]
when :labor
if costs[h[:type]].nil?
costs[h[:type]] = item_charge(h[:text])
else
puts " You have already entered the labor charge"
next
end
when :item
costs[h[:type]] = (costs[h[:type]] ||= 0) + item_charge(h[:text])
end
end
costs
end
Вот возможный диалог.
enter_costs
Menu ID Menu Item
------- ----------
1 Add Item Charge
2 Add Labor Charge
0 Exit application
Enter Menu ID: 0
You have not yet entered a item cost
< menu redisplayed >
Enter Menu ID: 1
Enter item amount: $2.50
< menu redisplayed >
Enter Menu ID: 0
You have not yet entered a labor cost
< menu redisplayed >
Enter Menu ID: 9
That is not a valid menu id
< menu redisplayed >
Enter Menu ID: 2
Enter labor amount: $1345.61
< menu redisplayed >
Enter Menu ID: 1
Enter item amount: $3.12
< menu redisplayed >
Enter Menu ID: 2
You have already entered the labour charge
< menu redisplayed >
Enter Menu ID: 0
#=> {:item=>5.62, :labor=>1345.61}
Обратите внимание, что нумерация элементов в меню выполняется в коде, поэтому изменения на MENU
не потребуют изменения номеров этих пунктов меню где-либо еще в код.
Я предлагаю использовать Kernel # l oop вместе с ключевым словом break , для всех циклов, вместо while
, until
и (особенно) for
петли.