A better idiomatic way of doing this?!

T

Tim Romberg

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
 
R

Rahul Kumar

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
 
T

Tim Romberg

Rahul said:
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!
 
B

Brian Candler

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

Tim Romberg

Brian said:
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!
 
B

Brian Candler

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

David Masover

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

Tim Romberg

Brian said:
@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 :eek:ptions

# 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
 
A

Adam Prescott

@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 wa= s
to output a menu through a module.

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

module Menu
=C2=A0def self.whatever
=C2=A0 =C2=A0puts "Hello"
=C2=A0end
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 =3D Menu.new ["Checkin","Checkout","Lists","Economy","Exit"]
m.main_menu
m =3D Menu.new ["list current guests","list all guests","back to main
menu"]
=C2=A0m.lists_menu

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

main_menu =3D Menu.new ["Checkin","Checkout","Lists","Economy","Exit"]
lists_menu =3D 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 =3D Menu["Checkin","Checkout","Lists","Economy","Exit"]
 
T

Tim Romberg

Adam said:
@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 :eek:ptions

# 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
 
T

Tim Romberg

Brian said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top