Use a string as a method call

Discussion in 'Ruby' started by Chris Bailey, Aug 28, 2008.

  1. Chris Bailey

    Chris Bailey Guest

    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/.
     
    Chris Bailey, Aug 28, 2008
    #1
    1. Advertising

  2. Chris Bailey

    Glen Holcomb Guest

    [Note: parts of this message were removed to make it a legal post.]

    On Thu, Aug 28, 2008 at 2:02 PM, 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?
    > --
    > 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.

    --
    "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)
     
    Glen Holcomb, Aug 28, 2008
    #2
    1. Advertising

  3. Chris Bailey

    Thomas B. Guest

    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/.
     
    Thomas B., Aug 28, 2008
    #3
  4. Chris Bailey

    Thomas B. Guest

    Thomas B. wrote:
    > send(input)


    Sorry, more like send:)"do_#{input}") in your example.
    TPR.
    --
    Posted via http://www.ruby-forum.com/.
     
    Thomas B., Aug 28, 2008
    #4
  5. On Thu, Aug 28, 2008 at 4:02 PM, 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()
    > }



    ACTIONS = %w[foo bar]

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




    --
    Technical Blaag at: http://blog.majesticseacreature.com | Non-tech
    stuff at: http://metametta.blogspot.com
     
    Gregory Brown, Aug 28, 2008
    #5
  6. 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....:)

    Cheers

    John
    --
    Posted via http://www.ruby-forum.com/.
     
    John Pritchard-williams, Aug 28, 2008
    #6
  7. 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
     
    Stefano Crocco, Aug 28, 2008
    #7
  8. 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:
    ICQ: 205544826
     
    Sebastian Hungerecker, Aug 28, 2008
    #8
  9. 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


    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
     
    Stefano Crocco, Aug 28, 2008
    #9
  10. Even better! You gotta love Ruby _even_ more for that :) (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/.
     
    John Pritchard-williams, Aug 28, 2008
    #10
  11. Chris Bailey

    Chris Bailey Guest

    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, Aug 28, 2008
    #11
  12. Chris Bailey

    Thomas B. Guest

    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., Aug 28, 2008
    #12
  13. Chris Bailey

    Chris Bailey Guest

    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/.
     
    Chris Bailey, Aug 28, 2008
    #13
  14. 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


    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.
     
    David Masover, Aug 29, 2008
    #14
  15. 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[x](input))

    with no luck. Any ideas? Thanks a bunch for any assistance anyone can
    provide :)
    --
    Posted via http://www.ruby-forum.com/.
     
    Andraes Tsepesh, Jun 26, 2010
    #15
  16. 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/.
     
    Andraes Tsepesh, Jun 26, 2010
    #16
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. =?ISO-8859-1?Q?Thomas_Gagn=E9?=
    Replies:
    9
    Views:
    11,225
    Jezuch
    Jul 3, 2003
  2. Ian
    Replies:
    2
    Views:
    346
    Ulrich Petri
    Sep 6, 2003
  3. Mac
    Replies:
    1
    Views:
    594
  4. =?ISO-8859-1?Q?Martin_J=F8rgensen?=
    Replies:
    5
    Views:
    1,303
    =?ISO-8859-1?Q?Martin_J=F8rgensen?=
    May 6, 2006
  5. ankur
    Replies:
    1
    Views:
    12,789
    Jan =?UTF-8?B?VGhvbcOk?=
    Aug 27, 2007
Loading...

Share This Page