How to pass a hash as a param to a method called through eval?

Discussion in 'Ruby' started by Alex Stahl, Jul 11, 2010.

  1. Alex Stahl

    Alex Stahl Guest

    Hi Folks - I've got a data-driven app I'm building, and I'd like to be
    able to read a set of data from a json file, pass that to eval, and have
    it executed:

    json:
    {
    "action": "someFunc",
    "params": {
    "a": "foo",
    "b": "bar",
    "c": "etc"
    }
    }

    call = JSON.parse(json)
    eval("#{call['action']} #{call['params']}")


    Problem is that 'call['params']' is treated as a string by the receiver,
    not the hash I intended to pass. Tried using casting operations first,
    like .to_s and then .to_hash, but the to_hash call fails w/ no method
    error. Instead the hash comes through as a string. How can I pass it
    so that it remains a hash, and retains its structure for key/val reading
    in the receiver?

    Thanks,
    Alex
    Alex Stahl, Jul 11, 2010
    #1
    1. Advertising

  2. Alex Stahl

    Ammar Ali Guest

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

    On Sun, Jul 11, 2010 at 10:57 PM, Alex Stahl <> wrote:

    > How can I pass it
    > so that it remains a hash, and retains its structure for key/val reading
    > in the receiver?
    >
    > Thanks,
    > Alex
    >



    Adding parentheses around #{call['params']} should do it:

    eval("#{call['action']}(#{call['params']})")

    Ammar
    Ammar Ali, Jul 11, 2010
    #2
    1. Advertising

  3. Alex Stahl

    Alex Stahl Guest

    Thanks, but... tried that already and it fails without even calling the
    method:

    "...undefined method `com' for nil:NilClass (NoMethodError)"

    So I tried that w/ single quotes too:

    eval("#{call['action']}('#{call['params']}')")

    and that works, but still passes the params as a string, not a hash. :(

    -Alex


    On Sun, 2010-07-11 at 15:18 -0500, Ammar Ali wrote:
    > On Sun, Jul 11, 2010 at 10:57 PM, Alex Stahl <> wrote:
    >
    > > How can I pass it
    > > so that it remains a hash, and retains its structure for key/val reading
    > > in the receiver?
    > >
    > > Thanks,
    > > Alex
    > >

    >
    >
    > Adding parentheses around #{call['params']} should do it:
    >
    > eval("#{call['action']}(#{call['params']})")
    >
    > Ammar
    Alex Stahl, Jul 11, 2010
    #3
  4. Alex Stahl wrote:
    > Hi Folks - I've got a data-driven app I'm building, and I'd like to be
    > able to read a set of data from a json file, pass that to eval, and have
    > it executed:
    >
    > json:
    > {
    > "action": "someFunc",
    > "params": {
    > "a": "foo",
    > "b": "bar",
    > "c": "etc"
    > }
    > }
    >
    > call = JSON.parse(json)
    > eval("#{call['action']} #{call['params']}")


    I'm pretty sure that what you really want is this:

    send(call['action'], call['params'])

    For simple cases you might be able to work with eval, like this:

    eval("#{call['action']} #{call['params'].inspect}")

    But that's fragile, slow, and fraught with security dangers. If what you
    want is to call a method whose name is in a variable, then the tool is
    provided to do that: 'send'
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Jul 11, 2010
    #4
  5. Alex Stahl

    Ammar Ali Guest

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

    On Sun, Jul 11, 2010 at 11:29 PM, Alex Stahl <> wrote:

    > Thanks, but... tried that already and it fails without even calling the
    > method:
    >
    > "...undefined method `com' for nil:NilClass (NoMethodError)"
    >
    > So I tried that w/ single quotes too:
    >
    > eval("#{call['action']}('#{call['params']}')")
    >
    > and that works, but still passes the params as a string, not a hash. :(
    >



    The argument should be a string, that's what eval expects. The problem with
    the first version (without the parentheses) was syntax. I don't know where
    the "com" or the nil:NilClass are coming from. Is there something missing
    from your code sample?

    Here's what I get in irb:

    mini:~ ammar$ rvm use 1.9.1
    info: Using ruby 1.9.1 p378
    mini:~ ammar$ irb
    ruby-1.9.1-p378 > require 'json'
    => true
    ruby-1.9.1-p378 > def some_func(hash); puts "from function:
    #{hash.inspect}"; end
    => nil
    ruby-1.9.1-p378 > j = '{ "action": "some_func", "params": { "a": "foo", "b":
    "bar" } }'
    => "{ \"action\": \"some_func\", \"params\": { \"a\": \"foo\", \"b\":
    \"bar\" } }"
    ruby-1.9.1-p378 > call = JSON.parse(j)
    => {"action"=>"some_func", "params"=>{"a"=>"foo", "b"=>"bar"}}
    ruby-1.9.1-p378 > eval("#{call['action']}(#{call['params']})")
    from function: {"a"=>"foo", "b"=>"bar"}
    => nil

    Ammar
    Ammar Ali, Jul 11, 2010
    #5
  6. Just use 'puts' instead of 'eval' to see what's happening.

    >> json = <<EOS

    {
    "action": "someFunc",
    "params": {
    "a": "foo",
    "b": "bar",
    "c": "etc"
    }
    }
    EOS
    => "{\n \"action\": \"someFunc\",\n \"params\": {\n \"a\":
    \"foo\",\n \"b\": \"bar\",\n \"c\": \"etc\"\n }\n}\n"
    >> require 'rubygems'

    => true
    >> require 'json'

    => true
    >> call = JSON.parse(json)

    => {"action"=>"someFunc", "params"=>{"a"=>"foo", "b"=>"bar",
    "c"=>"etc"}}
    >> puts "#{call['action']} #{call['params']}"

    someFunc afoobbarcetc
    => nil
    >> puts "#{call['action']}('#{call['params']}')"

    someFunc('afoobbarcetc')
    => nil
    >>


    Should be pretty obvious now, remember that eval is just interpreting
    that string as a piece of ruby code.
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Jul 11, 2010
    #6
  7. Alex Stahl

    yermej Guest

    On Jul 11, 2:57 pm, Alex Stahl <> wrote:
    [...]
    > Problem is that 'call['params']' is treated as a string by the receiver,
    > not the hash I intended to pass.  Tried using casting operations first,
    > like .to_s and then .to_hash, but the to_hash call fails w/ no method
    > error.  Instead the hash comes through as a string.  How can I pass it
    > so that it remains a hash, and retains its structure for key/val reading
    > in the receiver?
    >


    This will do it:

    eval("#{call['action']}(call['params'])")

    Depending on context, you can probably completely avoid using eval:

    method(call['action']).call(call['params'])

    When you use #{call['params']}, I think that calls #to_s on the Hash
    which causes what you're seeing.

    Jeremy
    yermej, Jul 11, 2010
    #7
  8. Hi --

    On Mon, 12 Jul 2010, Brian Candler wrote:

    > Just use 'puts' instead of 'eval' to see what's happening.
    >
    >>> json = <<EOS

    > {
    > "action": "someFunc",
    > "params": {
    > "a": "foo",
    > "b": "bar",
    > "c": "etc"
    > }
    > }
    > EOS
    > => "{\n \"action\": \"someFunc\",\n \"params\": {\n \"a\":
    > \"foo\",\n \"b\": \"bar\",\n \"c\": \"etc\"\n }\n}\n"
    >>> require 'rubygems'

    > => true
    >>> require 'json'

    > => true
    >>> call = JSON.parse(json)

    > => {"action"=>"someFunc", "params"=>{"a"=>"foo", "b"=>"bar",
    > "c"=>"etc"}}
    >>> puts "#{call['action']} #{call['params']}"

    > someFunc afoobbarcetc
    > => nil
    >>> puts "#{call['action']}('#{call['params']}')"

    > someFunc('afoobbarcetc')
    > => nil
    >>>

    >
    > Should be pretty obvious now, remember that eval is just interpreting
    > that string as a piece of ruby code.


    Footnote: in 1.9, Hash#to_s has changed such that (like Array#to_s) it
    returns more of an inspect string:

    >> puts "#{call['action']}(#{call['params']})"

    someFunc({"a"=>"foo", "b"=>"bar", "c"=>"etc"})

    (I concur however in your point that send is almost certainly a better
    choice anyway.)


    David

    --
    David A. Black, Senior Developer, Cyrus Innovation Inc.

    The Ruby training with Black/Brown/McAnally
    Compleat Stay tuned for next event announcement!
    Rubyist http://www.compleatrubyist.com
    David A. Black, Jul 11, 2010
    #8
  9. Alex Stahl

    Alex Stahl Guest

    Thanks. You're actually the second response to suggest doing it that
    way (w/ eval). But it doesn't work for me.

    Though, the first respondent is using 1.9.1, and I've got 1.8.7 at the
    moment. Are you by chance also on 1.9.1?

    -Alex

    On Sun, 2010-07-11 at 16:05 -0500, yermej wrote:
    > On Jul 11, 2:57 pm, Alex Stahl <> wrote:
    > [...]
    > > Problem is that 'call['params']' is treated as a string by the receiver,
    > > not the hash I intended to pass. Tried using casting operations first,
    > > like .to_s and then .to_hash, but the to_hash call fails w/ no method
    > > error. Instead the hash comes through as a string. How can I pass it
    > > so that it remains a hash, and retains its structure for key/val reading
    > > in the receiver?
    > >

    >
    > This will do it:
    >
    > eval("#{call['action']}(call['params'])")
    >
    > Depending on context, you can probably completely avoid using eval:
    >
    > method(call['action']).call(call['params'])
    >
    > When you use #{call['params']}, I think that calls #to_s on the Hash
    > which causes what you're seeing.
    >
    > Jeremy
    >
    Alex Stahl, Jul 11, 2010
    #9
  10. Alex Stahl

    Ammar Ali Guest

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

    On Mon, Jul 12, 2010 at 12:41 AM, Alex Stahl <> wrote:

    > Thanks. You're actually the second response to suggest doing it that
    > way (w/ eval). But it doesn't work for me.
    >
    > Though, the first respondent is using 1.9.1, and I've got 1.8.7 at the
    > moment. Are you by chance also on 1.9.1?



    On 1.8.7 use yermej's suggestion, without the interpolation, if you choose
    to stick with eval despite the excellent suggestions to use send instead:

    eval("#{call['action']}(call['params'])")

    Cheers,
    Ammar
    Ammar Ali, Jul 11, 2010
    #10
  11. Alex Stahl

    Alex Stahl Guest

    Thanks again, yermej's suggestion actually worked out. I've now got
    send doing exactly what I wanted to accomplish.

    Going into my thinking on this problem, I knew I wanted dynamic
    execution, and being relatively new to ruby, thought that eval would be
    the right tool. Wasn't aware of send. Great thing is it caused me to
    look up the Object and see all the cool things it can do for me.

    So my original question probably should have been, "is eval even the
    right tool for this?". Of course, that's not always obvious considering
    there's always more than one way to do something in ruby.

    On Sun, 2010-07-11 at 16:58 -0500, Ammar Ali wrote:
    > On Mon, Jul 12, 2010 at 12:41 AM, Alex Stahl <> wrote:
    >
    > > Thanks. You're actually the second response to suggest doing it that
    > > way (w/ eval). But it doesn't work for me.
    > >
    > > Though, the first respondent is using 1.9.1, and I've got 1.8.7 at the
    > > moment. Are you by chance also on 1.9.1?

    >
    >
    > On 1.8.7 use yermej's suggestion, without the interpolation, if you choose
    > to stick with eval despite the excellent suggestions to use send instead:
    >
    > eval("#{call['action']}(call['params'])")
    >
    > Cheers,
    > Ammar
    Alex Stahl, Jul 11, 2010
    #11
  12. Alex Stahl wrote:
    > So my original question probably should have been, "is eval even the
    > right tool for this?". Of course, that's not always obvious considering
    > there's always more than one way to do something in ruby.


    Certainly. You should also be aware that 'send' is also not without its
    security problems:


    $ irb --simple-prompt
    >> RUBY_DESCRIPTION

    => "ruby 1.8.7 (2010-01-10 patchlevel 249) [x86_64-linux]"
    >> foo = "system"

    => "system"
    >> bar = "rm /tmp/nonexistent*"

    => "rm /tmp/nonexistent*"
    >> send(foo,bar)

    rm: cannot remove `/tmp/nonexistent*': No such file or directory
    => false


    $ irb19 --simple-prompt
    >> RUBY_DESCRIPTION

    => "ruby 1.9.2dev (2009-07-18 trunk 24186) [i686-linux]"
    >> foo = "system"

    => "system"
    >> bar = "rm /tmp/nonexistent*"

    => "rm /tmp/nonexistent*"
    >> send(foo,bar)

    rm: cannot remove `/tmp/nonexistent*': No such file or directory
    => false


    So it may be a good idea to give all your callable methods a prefix
    ("do_xxx"), and/or only call public methods:

    send(foo,bar) if respond_to?(foo)
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Jul 12, 2010
    #12
  13. On 11.07.2010 23:00, Brian Candler wrote:
    > Just use 'puts' instead of 'eval' to see what's happening.
    >
    >>> json =<<EOS

    > {
    > "action": "someFunc",
    > "params": {
    > "a": "foo",
    > "b": "bar",
    > "c": "etc"
    > }
    > }
    > EOS
    > => "{\n \"action\": \"someFunc\",\n \"params\": {\n \"a\":
    > \"foo\",\n \"b\": \"bar\",\n \"c\": \"etc\"\n }\n}\n"
    >>> require 'rubygems'

    > => true
    >>> require 'json'

    > => true
    >>> call = JSON.parse(json)

    > => {"action"=>"someFunc", "params"=>{"a"=>"foo", "b"=>"bar",
    > "c"=>"etc"}}
    >>> puts "#{call['action']} #{call['params']}"

    > someFunc afoobbarcetc
    > => nil
    >>> puts "#{call['action']}('#{call['params']}')"

    > someFunc('afoobbarcetc')
    > => nil
    >>>

    >
    > Should be pretty obvious now, remember that eval is just interpreting
    > that string as a piece of ruby code.


    I wouldn't even use eval here - it's unsafe and slow. Something like
    this should work

    send(call['action'], *call['params'])

    Kind regards

    robert

    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
    Robert Klemme, Jul 12, 2010
    #13
  14. Alex Stahl

    Josh Cheek Guest

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

    Eval is also a wonky work flow.

    You have data serialized as JSON in a String
    You parse it into a Ruby hash
    You then serialize it as Ruby code in a String
    You then eval the string to get the hash back out of it...


    I don't know what types JSON supports, but if it supports anything that
    doesn't have a literal, then that second converting to String and evaling
    will break. Also explains why Amir's solution breaks on 1.8, because, as
    David A. Black pointed out, Hash#to_s changed, and that is what is being
    used to serialize it (which implies to me that this may not be realized)

    mini:~ ammar$ rvm use 1.9.1
    info: Using ruby 1.9.1 p378
    mini:~ ammar$ irb
    ruby-1.9.1-p378 > require 'json'
    => true
    ruby-1.9.1-p378 > def some_func(hash); puts "from function:
    #{hash.inspect}"; end
    => nil
    ruby-1.9.1-p378 > j = '{ "action": "some_func", "params": { "a": "foo", "b":
    "bar" } }'
    => "{ \"action\": \"some_func\", \"params\": { \"a\": \"foo\", \"b\":
    \"bar\" } }"
    ruby-1.9.1-p378 > call = JSON.parse(j)
    => {"action"=>"some_func", "params"=>{"a"=>"foo", "b"=>"bar"}}
    ruby-1.9.1-p378 > eval("#{call['action']}(#{
    call['params']})")
    from function: {"a"=>"foo", "b"=>"bar"}
    => nil
    Josh Cheek, Jul 12, 2010
    #14
    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. Geathaa
    Replies:
    2
    Views:
    693
    Geathaa
    Jul 30, 2003
  2. ectoplasm
    Replies:
    12
    Views:
    637
    Zorro
    Jul 28, 2005
  3. rp
    Replies:
    1
    Views:
    520
    red floyd
    Nov 10, 2011
  4. Replies:
    2
    Views:
    662
    Dave Weaver
    Feb 18, 2009
  5. Dave
    Replies:
    5
    Views:
    659
    John Bokma
    Apr 26, 2011
Loading...

Share This Page