Use a string as a method call

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
    do_foo()
elsif input == bar
    do_bar()
else
    puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
            'foo' => do_foo(),
            'bar' => do_bar()
           }

I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?

···

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

You could do it with eval in this case it sounds like that would be fairly
safe although you could also use a proc I think.

···

On Thu, Aug 28, 2008 at 2:02 PM, Chris Bailey < christopher.sean.bailey@gmail.com> wrote:

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
   do_foo()
elsif input == bar
   do_bar()
else
   puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
           'foo' => do_foo(),
           'bar' => do_bar()
          }

I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?
--
Posted via http://www.ruby-forum.com/\.

--
"Hey brother Christian with your high and mighty errand, Your actions speak
so loud, I can't hear a word you're saying."

-Greg Graffin (Bad Religion)

Chris Bailey wrote:

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
    do_foo()
elsif input == bar
    do_bar()
else
    puts "That isn't a command!"
end

ALLOWED=[:foo,:bar]
input=gets.downcase.chomp.to_sym
if ALLOWED.include? input
  send(input)
else
  puts "That isn't a command!"
end

The method send calls a method specified as the first argument to send.
Try send(:puts,"abc") as an example.

TPR.

···

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

ACTIONS = %w[foo bar]

def execute(action)
  return send("do_#{action}") if ACTIONS.include?(action)
  raise "Unexpected action"
end

···

On Thu, Aug 28, 2008 at 4:02 PM, Chris Bailey <christopher.sean.bailey@gmail.com> wrote:

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
   do_foo()
elsif input == bar
   do_bar()
else
   puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
           'foo' => do_foo(),
           'bar' => do_bar()
          }

--
Technical Blaag at: http://blog.majesticseacreature.com | Non-tech
stuff at: http://metametta.blogspot.com

Hi Chris,

Try this:

...
class MyClass
        def mymethod
                puts "mymethod called!"
        end

        def myothermethod
                puts "myothermethod called!"
        end
end

input = gets.downcase.chomp;
myinstance=MyClass.new;
myinstance.send(input.to_sym);
...

So use the 'send' method of your instance to invoke the method - first
convert the inputted string to a symbol. Wow..you gotta love Ruby for
stuff like this....:slight_smile:

Cheers

John

···

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

You can do something like this:

method_name = gets.downcase.chomp
send method_name

send is a method which takes as a first argument a method name (as a string or
symbol) and calls that method on its receiver, passing all the remaining
arguments to it (see ri Object#send for a better explaination). To better
handle the possibility the user inserts a wrong string, you can rescue the
NoMethodError exception:

method_name = gets.downcase.chomp
begin
  send method_name
rescue NoMethodError
  puts "#{method_name} is not a valid command"
end

If you want to restrict the methods the user can call, store them in an array
and check whether the string he entered is there before calling send:

commands = %w[foo bar]
method_name = gets.downcase.chomp
if commands.include? method_name then send method_name
else puts "#{method_name} is not a valid command"
end

I hope this helps

Stefano

···

On Thursday 28 August 2008, Chris Bailey wrote:

I'm trying to come up with an efficient way of using user input as a
way of calling methods. I'm unhappy with the way that I am doing it
because it isn't very flexible. This is what I'm doing now.

input = gets.downcase.chomp

if input == foo
    do_foo()
elsif input == bar
    do_bar()
else
    puts "That isn't a command!"
end

What I would like to do is more like so.

commands = {
            'foo' => do_foo(),
            'bar' => do_bar()
           }

I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?

Chris Bailey wrote:

I then would like to search the commands hash for a key that matches the
player input and execute the method associated with that key. What I've
noticed is that upon initialization of the hash the value becomes equal
to the result of the method but that is not what I want. If I store the
value as a string ie "do_foo()" would I be able to parse that string and
execute it as a method? And if so, how would that be done?

First time poster, and I was so surprised I was able to find this
online. I've been working on figuring this out for hours. I've adopted
the "send(value)" solution and it works great.

My question is, how can I pass arguments to methods called this way? My
hash is pretty much identical, here's a sample:

commands = {"look" => "doLook"}

I have split the input variable so I can accept more than 1 word at a
time (input can be anything from ["look"] to ["look", "at", "the",
"desk"]). I'd like to be able to pass the input variable to these
methods, but am having difficulty with that. I have tried modifying my
hash as so:

commands = {"look" => "doLook(input)"}

with no luck. I've also modified my send as so:

send(commands(input))

with no luck. Any ideas? Thanks a bunch for any assistance anyone can
provide :slight_smile:

···

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

Thomas B. wrote:

  send(input)

Sorry, more like send(:"do_#{input}") in your example.
TPR.

···

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

John Pritchard-williams wrote:

So use the 'send' method of your instance to invoke the method - first
convert the inputted string to a symbol.

Actually you don't need to do that. send takes strings too.

HTH,
Sebastian

···

--
NP: Porcupine Tree - Sleep Together
Jabber: sepp2k@jabber.org
ICQ: 205544826

Of course, with this approach, you'll need to change the names of your methods
to foo and bar, or to change the definition of method_name to something like
this:

method_name = "do_#{gets.downcase.chomp}"

Stefano

···

On Thursday 28 August 2008, Stefano Crocco wrote:

On Thursday 28 August 2008, Chris Bailey wrote:
> I'm trying to come up with an efficient way of using user input as a
> way of calling methods. I'm unhappy with the way that I am doing it
> because it isn't very flexible. This is what I'm doing now.
>
> input = gets.downcase.chomp
>
> if input == foo
> do_foo()
> elsif input == bar
> do_bar()
> else
> puts "That isn't a command!"
> end
>
> What I would like to do is more like so.
>
> commands = {
> 'foo' => do_foo(),
> 'bar' => do_bar()
> }
>
> I then would like to search the commands hash for a key that matches the
> player input and execute the method associated with that key. What I've
> noticed is that upon initialization of the hash the value becomes equal
> to the result of the method but that is not what I want. If I store the
> value as a string ie "do_foo()" would I be able to parse that string and
> execute it as a method? And if so, how would that be done?

You can do something like this:

method_name = gets.downcase.chomp
send method_name

send is a method which takes as a first argument a method name (as a string
or symbol) and calls that method on its receiver, passing all the remaining
arguments to it (see ri Object#send for a better explaination). To better
handle the possibility the user inserts a wrong string, you can rescue the
NoMethodError exception:

method_name = gets.downcase.chomp
begin
  send method_name
rescue NoMethodError
  puts "#{method_name} is not a valid command"
end

If you want to restrict the methods the user can call, store them in an
array and check whether the string he entered is there before calling send:

commands = %w[foo bar]
method_name = gets.downcase.chomp
if commands.include? method_name then send method_name
else puts "#{method_name} is not a valid command"
end

I hope this helps

Stefano

Subtle memory leak here: A symbol, once defined, is never collected. (Unless
this has changed, but I don't see how it could...) They have many advantages,
and I'd still use them in code, but don't use them with user input. Instead:

ALLOWED = %w{foo bar}.map(&:freeze).freeze
input = gets.downcase.chomp
if ALLOWED.include? input
  send input
else
  puts "'#{input}' isn't a command!"
end

A possible performance hack is to use a Set instead, but I'm not sure how
large the array has to be for this to actually be faster:

require 'set'
ALLOWED = Set.new(%w{foo bar}).freeze

And yes, "send" seems to work with strings. No point in casting to a symbol if
you're only going to be using it for one send call.

Of course, I wouldn't worry about it if you're only accepting input from one
user, at the commandline -- they'd have to bang on the keyboard quite awhile
to use any significant amount of RAM.

But it's something to consider when building any kind of long-running service.

···

On Thursday 28 August 2008 15:08:01 Thomas B. wrote:

ALLOWED=[:foo,:bar]
input=gets.downcase.chomp.to_sym
if ALLOWED.include? input
  send(input)
else
  puts "That isn't a command!"
end

Andraes Tsepesh wrote:

My question is, how can I pass arguments to methods called this way? My
hash is pretty much identical, here's a sample:

Hah, I figured it out. This always happens - I have to formally type out
my request in a help forum before I can figure it out lol

···

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

Even better! You gotta love Ruby _even_ more for that :slight_smile: (Thanks !)

Sebastian Hungerecker wrote:

···

John Pritchard-williams wrote:

So use the 'send' method of your instance to invoke the method - first
convert the inputted string to a symbol.

Actually you don't need to do that. send takes strings too.

HTH,
Sebastian

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

Thank you everyone for the quick and helpful replies! I toyed around
with all of the suggestions and decided the following would be the best
choice in my application.

def parse
  input = gets.downcase.chomp
  if $cmd_list.key?(input)
    send $cmd_list[input]
  else
    puts "#{input} is not a valid command."
  end
end

I have however ran into another problem and my limited knowledge of send
(I hadn't heard of it until I read these replies) makes it difficult for
me to debug. Some of my methods that worked perfectly before accessing
them using send are no longer working properly. Consider the following.

cmd_list = {
            'north' => 'do_north'
            'south' => 'do_south'
            'n' => 'do_north'
            's' => 'do_south'
            'look' => 'do_look'
           }

def cmd_north
  $PLROBJ.xcoord -=
  showMap()
end

def cmd_south
  $PLROBJ.xcoord +=
  showMap()
end

def cmd_look
  showMap
end

If the user input is "look", the showMap method is called properly, and
everything works as intended. However if north,south,n or s is typed
into the system it fails to update the $PLROBJ variable but executes
showMap() as usual and then crashes with the following error.

./commands.rb:16:in `-': nil can't be coerced into Fixnum (TypeError)
  from ./commands.rb:16:in `cmd_north'
  from bsud.rb:33:in `send'
  from bsud.rb:33:in `parse'
  from bsud.rb:58:in `main'
  from bsud.rb:63

$PLROBJ.xcoord and ycoord are initiated with a value of 5 and as far as
I know there is nothing that would ever make them 'nil'. I have no idea
what this problem is =(

···

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

Chris Bailey wrote:

    send $cmd_list[input]

cmd_list = {
            'north' => 'do_north'
            'south' => 'do_south'
            'n' => 'do_north'
            's' => 'do_south'
            'look' => 'do_look'
           }

The variable names don't agree but I suppose it's a typo in the post?

Well, what are you trying to do? If you want to increment or decrement
$PLROBJ.xcoord, you must say $PLROBJ.xcoord+=1 or $PLROBJ.xcoord-=1.
What happens now, the variable $PLROBJ.xcoord is incremented or
decremented by the result of showMap, like in $PLROBJ.xcoord-=showMap(),
and showMap probably returns nil.

TPR

···

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

Thomas B. wrote:

Chris Bailey wrote:

    send $cmd_list[input]

cmd_list = {
            'north' => 'do_north'
            'south' => 'do_south'
            'n' => 'do_north'
            's' => 'do_south'
            'look' => 'do_look'
           }

The variable names don't agree but I suppose it's a typo in the post?

Well, what are you trying to do? If you want to increment or decrement
$PLROBJ.xcoord, you must say $PLROBJ.xcoord+=1 or $PLROBJ.xcoord-=1.
What happens now, the variable $PLROBJ.xcoord is incremented or
decremented by the result of showMap, like in $PLROBJ.xcoord-=showMap(),
and showMap probably returns nil.

TPR

Arg! The variable mismatch was a typo, and the error is because I was
using += instead of +=1. Dumb mistake on my part. Yesterday I was trying
to use ++
Thanks

···

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