0

At the end of this code, I'm trying to prevent the user from inputting another entry for menuID 2. I need to prompt that they've already made an election and then redisplay my table.

I'm aware my else statement is incorrect.

menuID = -1
totalPrice = 0
while (menuID != 0)
  puts ("Menu ID \tMenu Item")
  puts ("-------\t\t----------")
  puts ("1\t\tAdd Item Charge")
  puts ("2\t\tAdd Labor Charge")
  puts ("3\t\tApply Discount")
  puts ("4\t\tApply Gift Card")
  puts ("5\t\tTotal") 
  puts ("9\t\tNew Transaction")
  puts ("0\t\tExit Application")


  print ("Enter Menu ID: ")
  menuID = gets().to_i()
  if (menuID == 1)
    print ("Enter item's charge amount: $")
    chargeAmount = gets().to_f()
    chargeAmount = chargeAmount.round(2)
    totalPrice += chargeAmount
    if (chargeAmount <= 0)
      puts ("Item charge amount must be greater than 0. You entered $#{chargeAmount}")
    end

  elsif (menuID == 2)
    print ("Enter labor charge amount: $")
    laborCharge = gets().to_f()
    laborCharge = laborCharge.round(2)
    if (laborCharge <= 0)
      puts ("Labor charge amount must be greater than 0. You entered $#{laborCharge}")
    else
      puts ("Labor charge has already been applied")
    end
  end

end
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
cellery13
  • 5
  • 2
  • Welcome to SO. Please read "[How do I format my posts...](https://stackoverflow.com/help/formatting)" and "[How do I format my code blocks?](https://meta.stackexchange.com/questions/22186/)". Properly formatting your question (or answer) is important on SO as it helps us more quickly help you, and helps others when they're searching for a solution to the same problem. Also "[Writing The Perfect Question](https://codeblog.jonskeet.uk/2010/08/29/writing-the-perfect-question/)" and "[How To Ask Questions The Smart Way](http://catb.org/esr/faqs/smart-questions.html)" will help when asking. – the Tin Man Feb 15 '20 at 20:39
  • In Ruby we don't use empty parenthesis for methods. `gets().to_i()` should be `gets.to_i`. Also, you're using camelCase for variables but should use snake_case; ItIsAReadability_thing. [Rubocop](https://github.com/rubocop-hq/ruby-style-guide) would be good for you to investigate. – the Tin Man Feb 15 '20 at 20:47

2 Answers2

1

Here's a basic structure you could use.

Menu

First let's create a helper method to display the menu. Doing so speeds testing and makes it easier to change the menu in future.

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

Let's try it.

display_menu
Menu ID         Menu Item
-------         ----------
1               Add Item Charge
2               Add Labor Charge
0               Exit application

Note that to make changes to the menu items only MENU needs to be altered.

For now, disregard the keys :text and :type in MENU.

Adding charges

If the user wishes to add an item charge the following method will be called with an argument that is a label for the charge type and will return a valid dollar amount:

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

See Kernel::Float. This method was given the :exception option in Ruby v2.7. Earlier versions raised an exception if the string could not be converted to a float. That exception would have to be rescued and nil returned.

Here's a possible dialogue:

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 

Save charges

Now let's create a hash to hold the total cost of items and the cost of labor, with values initialized to nil.

costs = MENU.each_with_object({}) { |g,h| h[g[:type]] = nil }
  #=> {:item=>nil, :labor=>nil}

This will be the first line of the method enter_costs below.

OK to exit?

Let's assume we don't want to allow the user to exit the application before they have entered a labor charge and at least one item charge. We might write a separate method to see if they meet that requirement:

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         

See Enumerable#find.

Is menu selection valid?

We need to see if numerical value entered for the menu id selection (other than zero, which we have already checked for) is valid:

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 

Body

We can now put everything together.

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

Here is a possible dialog.

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}

Notice that the numbering of items in the menu is done in code, so that changes to MENU will not necessitate changes to those menu item numbers elsewhere in the code.

I suggest using Kernel#loop, together with keyword break, for all loops, in preference to while, until and (especially) for loops.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    Thank you! The breakdown of this really helps me understand the process of whats being done. My code is working succesfully now, thank you (: – cellery13 Feb 16 '20 at 18:26
0

You might want to maintain an array of menu_items and fill out the menuID for them every time they start entering a new item.

Remember that array items are 0-indexed, so you will want set menu_items[0] equal to the menu item with menuID 1, and menu_items[1] equal to the menu item with menuID 2, and so on.

C-RAD
  • 1,052
  • 9
  • 18
  • Yes, I realize I will have to do that. This works with the .push method, right? since I am originally initializing the array. But how do you assign the index when using .push? – cellery13 Feb 15 '20 at 23:28
  • push will always insert the item as the new, highest index. If you want to allow the user to choose the index, and you don't know what the maximum index will be, you might want to consider using a hash, where the keys are the integer indexes and the values are the menu items that would have gone into your array. – C-RAD Feb 15 '20 at 23:31
  • Hey C-RAD, Do you know how I could format a number such that any trailing zeros after the decimal point will show? I''m already rounding to two decimals but it drops the second decimal place if it ends in 0. – cellery13 Feb 16 '20 at 19:20
  • If you still haven’t gotten it, I think this question is what you are looking for: https://stackoverflow.com/questions/15900537/to-d-to-always-return-2-decimals-places-in-ruby. – C-RAD Feb 17 '20 at 01:53