A better idiomatic way of doing this?!

Hi Im new at Ruby and been struggling with this lab I have for a course
Im doing in Ruby. Im working on a program were you can register guests
and unregister and so on. What I need to do now is to present a menu
through a module which we can cal the main_menu. From there you should
be able to navigate to two other menus and back again to the main menu
this by user input. Until now Ive managed fine with the user input and
displaying a menu, but now as I have three menus but only want the main
to show I not really sure how to do this. Hopefully someone can help. I
have pasted the code below. Thanks!

module Menus

class Main_Menu
# This is a class method because of the “self”
def self.main_menu
puts “---------------------------”
puts " Menu"
puts " 1. Checkin"
puts " 2. Checkout"
puts " 3. Lists"
puts " 4. Economy"
puts " 5. Exit"
puts “”
puts " What do you want to do?"
puts “---------------------------”
print ": "
choice = get_input
make_choice(choice)
end
# fetches the menu choice and returns the chosen one
def self.get_input
input = gets.chomp.to_i

   while input > 5 || input < 1 do
       puts "Ooups wrong, please try again :)."
       input = gets.chomp.to_i
    end
    return input
  end

 def self.make_choice(choice)
   # chooses something from the menu based on the choice
    case choice
       when 1:
          check_in
       when 2:
          check_out
       when 3:
         puts $camping.current_guests
        when 4:
         puts $camping.all_guests
        when 5:
         puts "You are now leaving the camping, welcome back!"
         exit
       end
    end
  end
end

class Main_Menu
include Menus

Main_Menu.main_menu
end

Please look at the highline project. It will help you create menus, take
input, attach processing to menu items etc in a very clean and beautiful
way.

gem install highline
or check the rubyforge page highline.rubyforge.org

Rahul K. wrote:

Please look at the highline project. It will help you create menus, take
input, attach processing to menu items etc in a very clean and beautiful
way.

gem install highline
or check the rubyforge page highline.rubyforge.org

Thank you Rahul I heard about the highline project and have already
installed it. The problem though is that this project is for a course
and we have not had highline as a part of the course. In this case they
would have to install highline aswell to be able to evaluate my
assignment and I dont think they would approve. Do you have any other
tips?!
Thank you!

On Mon, Sep 06, 2010 at 06:12:39AM +0900, Tim Romberg wrote:

module Menus

class Main_Menu
# This is a class method because of the “self”
def self.main_menu
puts “---------------------------”

Is the issue that you want to be able to create multiple menus from the
same
code? Then make it data-driven.

Your current code never creates any instances of Main_Menu, but instead
defines methods on the class itself (e.g. def self.main_menu). If you
define
instance methods, then each instance can have its own data structure.
e.g.

class Menu

Pass in array of options

def initialize(options)
@options = options
end

def main_menu
puts “---------------------------”
puts " Menu"
@options.each_with_index do |item, i|
puts " #{i+1}. #{item}"
end
puts
puts “What do you want to do?”
#… etc
end
end

m = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
m.main_menu

Continue to make the rest of your code data-driven. e.g. Instead of

   while input > 5 || input < 1 do

you can write

   while input > @options.size || input < 1 do

I suggest that make_choice should move out of this class, and be the
responsibility of the caller to do something based on the returned
value.
(If you really wanted to integrate this you could pass in an array of
lambdas for code to be executed on each choice, but I don’t really see
the
need for this added complexity)

class Main_Menu
include Menus

I’ve no idea why you’re including Menus into the class - especially as
at
the moment you’re not creating any instances of the class. I’d drop
that.

Regards,

Brian.

On Tue, Sep 07, 2010 at 01:59:53AM +0900, Tim Romberg wrote:

@Brian: Thanks for your feedback. I am aware that I’m not practicing
“good” Ruby yet, I hope with more time I will develop. The reason why
included the module was because one of the musts with the assignment was
to output a menu through a module.

In your original code you only had class methods, so you could have
defined
them directly on a module instead:

module Menu
def self.whatever
puts “Hello”
end
end
Menu.whatever

This is because a class is a module, just with the extra abilities of
creating instances and subclassing, which you weren’t using.

When I want to create the other menus
as you stated with the main_menu example can I just continue creating
the menu objects equally?! For example:
m = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
m.main_menu
m = Menu.new [“list current guests”,“list all guests”,“back to main
menu”]
m.lists_menu

Sure; or store them in different variables (or constants) and then
re-use
them as you like. You might choose a more descriptive name for your
method
which prints the menu and lets the user choose a selection.

main_menu = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
lists_menu = Menu.new [“list current guests”,“list all guests”,“back to
main
menu”]
main_menu.choose
lists_menu.choose

Regards,

Brian.

Brian C. wrote:

On Mon, Sep 06, 2010 at 06:12:39AM +0900, Tim Romberg wrote:

module Menus

class Main_Menu
# This is a class method because of the “self”
def self.main_menu
puts “---------------------------”

Is the issue that you want to be able to create multiple menus from the
same
code? Then make it data-driven.

Your current code never creates any instances of Main_Menu, but instead
defines methods on the class itself (e.g. def self.main_menu). If you
define
instance methods, then each instance can have its own data structure.
e.g.

class Menu

Pass in array of options

def initialize(options)
@options = options
end

def main_menu
puts “---------------------------”
puts " Menu"
@options.each_with_index do |item, i|
puts " #{i+1}. #{item}"
end
puts
puts “What do you want to do?”
#… etc
end
end

m = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
m.main_menu

Continue to make the rest of your code data-driven. e.g. Instead of

   while input > 5 || input < 1 do

you can write

   while input > @options.size || input < 1 do

I suggest that make_choice should move out of this class, and be the
responsibility of the caller to do something based on the returned
value.
(If you really wanted to integrate this you could pass in an array of
lambdas for code to be executed on each choice, but I don’t really see
the
need for this added complexity)

class Main_Menu
include Menus

I’ve no idea why you’re including Menus into the class - especially as
at
the moment you’re not creating any instances of the class. I’d drop
that.

Regards,

Brian.

@Brian: Thanks for your feedback. I am aware that I’m not practicing
“good” Ruby yet, I hope with more time I will develop. The reason why
included the module was because one of the musts with the assignment was
to output a menu through a module. When I want to create the other menus
as you stated with the main_menu example can I just continue creating
the menu objects equally?! For example:
m = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
m.main_menu
m = Menu.new [“list current guests”,“list all guests”,“back to main
menu”]
m.lists_menu
etc.
Thanks for the help!

On Monday, September 06, 2010 01:06:00 am Tim Romberg wrote:

Rahul K. wrote:

Please look at the highline project. It will help you create menus, take
input, attach processing to menu items etc in a very clean and beautiful
way.

gem install highline
or check the rubyforge page highline.rubyforge.org

Thank you Rahul I heard about the highline project and have already
installed it. The problem though is that this project is for a course
and we have not had highline as a part of the course. In this case they
would have to install highline aswell to be able to evaluate my
assignment and I dont think they would approve.

Have you asked them? I’m currently taking a course on C and C++, and I
am
allowed to use libraries if I get approval for the specific library.

Frankly, there isn’t a more idiomatically-Ruby way to do this than to
find a
gem which solves the same problem.

On Mon, Sep 6, 2010 at 6:23 PM, Brian C. [email protected]
wrote:

On Tue, Sep 07, 2010 at 01:59:53AM +0900, Tim Romberg wrote:

@Brian: Thanks for your feedback. I am aware that I’m not practicing
“good” Ruby yet, I hope with more time I will develop. The reason why
included the module was because one of the musts with the assignment was
to output a menu through a module.

In your original code you only had class methods, so you could have defined
them directly on a module instead:

module Menu
 def self.whatever
  puts “Hello”
 end
end
Menu.whatever

This is because a class is a module, just with the extra abilities of
creating instances and subclassing, which you weren’t using.

When I want to create the other menus
as you stated with the main_menu example can I just continue creating
the menu objects equally?! For example:
m = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
m.main_menu
m = Menu.new [“list current guests”,“list all guests”,“back to main
menu”]
 m.lists_menu

Sure; or store them in different variables (or constants) and then re-use
them as you like. Â You might choose a more descriptive name for your method
which prints the menu and lets the user choose a selection.

main_menu = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
lists_menu = Menu.new [“list current guests”,“list all guests”,“back to main
menu”]
main_menu.choose
lists_menu.choose

Regards,

Brian.

I’d also like to add that this would be good for simplicity:

class Menu
def self.
self.new(*args)
end
end

to allow you to say

main_menu = Menu[“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]

Brian C. wrote:

On Tue, Sep 07, 2010 at 01:59:53AM +0900, Tim Romberg wrote:

@Brian: Thanks for your feedback. I am aware that I’m not practicing
“good” Ruby yet, I hope with more time I will develop. The reason why
included the module was because one of the musts with the assignment was
to output a menu through a module.

In your original code you only had class methods, so you could have
defined
them directly on a module instead:

module Menu
def self.whatever
puts “Hello”
end
end
Menu.whatever

This is because a class is a module, just with the extra abilities of
creating instances and subclassing, which you weren’t using.

When I want to create the other menus
as you stated with the main_menu example can I just continue creating
the menu objects equally?! For example:
m = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
m.main_menu
m = Menu.new [“list current guests”,“list all guests”,“back to main
menu”]
m.lists_menu

Sure; or store them in different variables (or constants) and then
re-use
them as you like. You might choose a more descriptive name for your
method
which prints the menu and lets the user choose a selection.

main_menu = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
lists_menu = Menu.new [“list current guests”,“list all guests”,“back to
main
menu”]
main_menu.choose
lists_menu.choose

Regards,

Brian.

@Brian: you really are a gem. Thanks for all your help. I tried to
follow what you said put found it hard to incorporate all of it. I did
like this but does not seem to work so well:

module Menus
def self.getValidNumber
input = gets.chomp

while input > @options.size || input < 1 do
  puts "cant do that tru again."
  input = gets.chomp
end

number = input.to_f
if (number <= 0)
  puts "cant state a negative value."
  getValidPositiveNumber
end
return number

end

def self.get_valid_input(options)

input = gets.chomp

while (!options.include?(input) && !options.include?(input.to_i))
  puts "No good, you have to choose a value between " + 

valid_options.inspect
input = gets.chomp
end
return input

end

class Menu

attr_reader :options

Pass in array of options

def initialize(options)
@options = options
end

def main_menu
puts “---------------------------”
puts " Main Menu"
@options.each_with_index do |item, i|
puts " #{i+1}. #{item}"
end
puts
puts “What do you want to do?”
end

def self.make_choice(choice)
# chooses something from the menu based on the choice
case choice
when 1:
check_in
when 2:
check_out
when 3:
puts $in_menu = lists_menu
when 4:
puts $in_menu = economy_menu
when 5:
puts “You are now leaving the camping, welcome back!”
exit
end
end
end

def lists_menu
puts “---------------------------”
puts " List Menu"
@options.each_with_index do |item, i|
puts " #{i+1}. #{item}"
end
puts
puts “What do you want to do?”
end

def self.make_choice(choice)
  case choice
    when 1
      puts $camping
    when 2
      puts $camping.history.all_guests
    when 0
      $in_menu = main.menu
  end
end

m = Menu.new [“Checkin”, “Checkout”,“Lists”,“Economy”,“Exit”]
m.main_menu
m = Menu.new [“List current guests”,“List all guests”,“back to main
menu”]
m.lists_menu

end

I get an undefined method lists_menu’ for #Menus::Menu:0x10019aa90
(NoMethodError)
I guess I have declared something wrong somewhere. Do not mean to make
you debug all of my code but could you maybe give a hint?! Thanks

Regards
Tim

On Tue, Sep 07, 2010 at 03:12:30AM +0900, Tim Romberg wrote:

@Brian: you really are a gem. Thanks for all your help. I tried to
follow what you said put found it hard to incorporate all of it. I did
like this but does not seem to work so well:

I get an undefined method lists_menu’ for #Menus::Menu:0x10019aa90
(NoMethodError)
I guess I have declared something wrong somewhere. Do not mean to make
you debug all of my code but could you maybe give a hint?! Thanks

If you reformat your code properly - see attached - it should become
clearer. You have your "end"s misplaced, so you’re defining lists_menu
outside of the class, but inside the module.

You might be able to find a good editor helps you with that, or there
are
utilities around for reformatting ruby source I think. If you have a
copy
of ruby 1.9 lying around, then running it with the -w flag will also
give
you hints where things aren’t aligned properly (I keep 1.9 around for
only
that purpose)

It would also be conventional to put your ‘main program’, the bit which
creates the menus and runs them, outside of the enclosing module. That
would
give you:

module Menus
class Menu

end
end
m = Menus::Menu.new

It’s a bit more unwieldy because of the use of the enclosing Module,
which
you have to use explicitly if you’re outside.

Regards,

Brian.

Adam P. wrote:

On Mon, Sep 6, 2010 at 6:23 PM, Brian C. [email protected]
wrote:

On Tue, Sep 07, 2010 at 01:59:53AM +0900, Tim Romberg wrote:

@Brian: Thanks for your feedback. I am aware that I’m not practicing
“good” Ruby yet, I hope with more time I will develop. The reason why
included the module was because one of the musts with the assignment was
to output a menu through a module.

In your original code you only had class methods, so you could have defined
them directly on a module instead:

module Menu
 def self.whatever
  puts “Hello”
 end
end
Menu.whatever

This is because a class is a module, just with the extra abilities of
creating instances and subclassing, which you weren’t using.

When I want to create the other menus
as you stated with the main_menu example can I just continue creating
the menu objects equally?! For example:
m = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
m.main_menu
m = Menu.new [“list current guests”,“list all guests”,“back to main
menu”]
 m.lists_menu

Sure; or store them in different variables (or constants) and then re-use
them as you like. Â You might choose a more descriptive name for your method
which prints the menu and lets the user choose a selection.

main_menu = Menu.new [“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]
lists_menu = Menu.new [“list current guests”,“list all guests”,“back to main
menu”]
main_menu.choose
lists_menu.choose

Regards,

Brian.

I’d also like to add that this would be good for simplicity:

class Menu
def self.
self.new(*args)
end
end

to allow you to say

main_menu = Menu[“Checkin”,“Checkout”,“Lists”,“Economy”,“Exit”]

@Adam: How exactly would you incorporate that?! Now the class menu is as
follows:

class Menu
attr_reader :options

Pass in array of options

def initialize(options)
@options = options
end

def main_menu
puts “---------------------------”
puts " Main Menu"
@options.each_with_index do |item, i|
puts " #{i+1}. #{item}"
end
puts
puts “What do you want to do?”
end

Regards
Tim

Brian C. wrote:

On Tue, Sep 07, 2010 at 03:12:30AM +0900, Tim Romberg wrote:

@Brian: you really are a gem. Thanks for all your help. I tried to
follow what you said put found it hard to incorporate all of it. I did
like this but does not seem to work so well:

I get an undefined method lists_menu’ for #Menus::Menu:0x10019aa90
(NoMethodError)
I guess I have declared something wrong somewhere. Do not mean to make
you debug all of my code but could you maybe give a hint?! Thanks

If you reformat your code properly - see attached - it should become
clearer. You have your "end"s misplaced, so you’re defining lists_menu
outside of the class, but inside the module.

You might be able to find a good editor helps you with that, or there
are
utilities around for reformatting ruby source I think. If you have a
copy
of ruby 1.9 lying around, then running it with the -w flag will also
give
you hints where things aren’t aligned properly (I keep 1.9 around for
only
that purpose)

It would also be conventional to put your ‘main program’, the bit which
creates the menus and runs them, outside of the enclosing module. That
would
give you:

module Menus
class Menu

end
end
m = Menus::Menu.new

It’s a bit more unwieldy because of the use of the enclosing Module,
which
you have to use explicitly if you’re outside.

Regards,

Brian.

@Brian: Thanks Brian you were right. I got that to role my only problem
is now that it currently displays both menus at the same time. I guess
thats logical as its calling both menu objects at the same time. i only
want the main menu to show when you start it, the other menus should
function as submenus. Im actually having my main in a seperate file and
it looks like this:
require ‘menu_test’
=begin
Main class for the program. Creates a new camping
and starts the loop fpr the program
=end
class Main

if FILE == $0
$camping = Camping.new(32, 12) # creates new camping

include Menus
$current_menu = main_menu

# loops through menu
while (true)
  puts $current_menu
  choice = Menus.get_input
  $current_menu.make_menu_choice(choice)
end

end
end

Buff…I fel like Im making things more complicated but Im laking the
proper design pattern skills for Ruby. I like what you said about:
module Menus
class Menu

end
end
m = Menus::Menu.new
and to use that as a main instead.

Big thanks and regards
Tim