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

···

--
Posted via http://www.ruby-forum.com/.

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

···

--
Posted via http://www.ruby-forum.com/.

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 Mon, Sep 06, 2010 at 06:12:39AM +0900, Tim Romberg wrote:

Rahul Kumar 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!

···

--
Posted via http://www.ruby-forum.com/\.

Brian Candler 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 Mon, Sep 06, 2010 at 06:12:39AM +0900, Tim Romberg wrote:

--
Posted via http://www.ruby-forum.com/\.

@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.

···

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

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 Monday, September 06, 2010 01:06:00 am Tim Romberg wrote:

Rahul Kumar 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.

Brian Candler 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 01:59:53AM +0900, Tim Romberg wrote:

--
Posted via http://www.ruby-forum.com/\.

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

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

to allow you to say

main_menu = Menu["Checkin","Checkout","Lists","Economy","Exit"]

···

On Mon, Sep 6, 2010 at 6:23 PM, Brian Candler <B.Candler@pobox.com> 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:

...

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.

menu.rb (1.88 KB)

···

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

Adam Prescott 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.(*args)
    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

···

On Mon, Sep 6, 2010 at 6:23 PM, Brian Candler <B.Candler@pobox.com> > wrote:

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

--
Posted via http://www.ruby-forum.com/\.

Brian Candler 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

···

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

--
Posted via http://www.ruby-forum.com/\.