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

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

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

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

Ammar

···

On Sun, Jul 11, 2010 at 10:57 PM, Alex Stahl <astahl@hi5.com> 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

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/\.

[...]

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

···

On Jul 11, 2:57 pm, Alex Stahl <ast...@hi5.com> 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. :frowning:

-Alex

···

On Sun, 2010-07-11 at 15:18 -0500, Ammar Ali wrote:

On Sun, Jul 11, 2010 at 10:57 PM, Alex Stahl <astahl@hi5.com> 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

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 <ast...@hi5.com> 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

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

···

On Sun, Jul 11, 2010 at 11:29 PM, Alex Stahl <astahl@hi5.com> 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. :frowning:

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

···

On Mon, Jul 12, 2010 at 12:41 AM, Alex Stahl <astahl@hi5.com> 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?

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/\.

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 <astahl@hi5.com> 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

Hi --

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

···

On Mon, 12 Jul 2010, Brian Candler wrote:

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

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/\.

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

···

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.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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