Q: How to convert hashed parameters to local variables?

In my efforts to write yet another template language {I know, I
know …grin}, I find myself wanting to provide simple named
parameters ala Mason. I’d like to insulate the user from the
mechanics of “dereferencing” the hashed parameters.

What I desire is an implementation for “parameters” such that:

def announce(args)
parameters(args,:age=>21)
"My name is #{first} #{last} and my age is #{age}"
end

announce(:first=>‘John’, :last=>‘Smith’) generates
"My name is John Smith and my age is 21"

I was thinking something like …

def parameters(args,defaults)
args=defaults.update(args)
args.each_pair do |key,value|
< generate a object with the name ‘key’ and value 'value’
in the scope of the calling routine >
end
end

But the <…> portion has eluded me. Any assistance would be
appreciated.

def announce(args)
parameters(args,:age=>21)
“My name is #{first} #{last} and my age is #{age}”
end

announce(:first=>‘John’, :last=>‘Smith’) generates
“My name is John Smith and my age is 21”

You can use:

args.each { |key,val| eval “#{key}=#{val}” }

However, this is extremely insecure if you allow joe user to create their own template. They could execute any code they want via it. (#{IO.readlines(‘/etc/passwd’).join}), for example. My suggestion is to write your own template style:

def announce(args,template)
template.gsub(/#((\w+))/) { args[$1] }
end

or similar.

  • Greg Millam

Dennis Misener wrote:

What I desire is an implementation for “parameters” such that:

def announce(args)
parameters(args,:age=>21)
“My name is #{first} #{last} and my age is #{age}”
end

announce(:first=>‘John’, :last=>‘Smith’) generates
“My name is John Smith and my age is 21”

The ostruct solution presented earlier is a nice one. Another
(eval-based) solution is to simply put each hash-key value in an
instance variable:

hash.each_pair do |key,value|
eval “@#{key} = value”
end

'Course, then you have to say “My name is #{@first} and my age is
#{@age}”…

···


Jamis Buck
jgb3@email.byu.edu

ruby -h | ruby -e ‘a=;readlines.join.scan(/-(.)[e|Kk(\S*)|le.l(…)e|#!(\S*)/) {|r| a << r.compact.first };puts “\n>#{a.join(%q/ /)}<\n\n”’

“Dennis Misener” dm@accesscable.net schrieb im Newsbeitrag
news:MPG.1a46f1de6fb17ed0989681@library.airnews.net

In my efforts to write yet another template language {I know, I
know …grin}, I find myself wanting to provide simple named
parameters ala Mason. I’d like to insulate the user from the
mechanics of “dereferencing” the hashed parameters.

What I desire is an implementation for “parameters” such that:

def announce(args)
parameters(args,:age=>21)
“My name is #{first} #{last} and my age is #{age}”
end

announce(:first=>‘John’, :last=>‘Smith’) generates
“My name is John Smith and my age is 21”

I was thinking something like …

def parameters(args,defaults)
args=defaults.update(args)
args.each_pair do |key,value|
< generate a object with the name ‘key’ and value ‘value’
in the scope of the calling routine >
end
end

But the <…> portion has eluded me. Any assistance would be
appreciated.

Binding and eval are your friends. But they do not fully solve your
problem, since local variables are checked syntactically for presence:

def test(hash)
value = nil
env = binding

hash.each do |key,value|
eval “#{key}=value”, env
end

puts “last value was #{value}”

error because “name” is syntactically checked for presence:

puts “name is #{name}”

hash.each do |key,value|
eval “printf "#{key}=%s\n", #{key}.to_s”, env
end
end

irb(main):219:0* test( { :foo => “bar”, :name => “Smith” } )
last value was Smith
foo=bar
name=Smith

Regards

robert

Dennis Misener wrote:

In my efforts to write yet another template language {I know, I
know …grin}, I find myself wanting to provide simple named
parameters ala Mason. I’d like to insulate the user from the
mechanics of “dereferencing” the hashed parameters.

What I desire is an implementation for “parameters” such that:

def announce(args)
parameters(args,:age=>21)
“My name is #{first} #{last} and my age is #{age}”
end

announce(:first=>‘John’, :last=>‘Smith’) generates
“My name is John Smith and my age is 21”

If you’re willing to add “{” and “}” in your announce method you could do:

def parameters(args,defaults,&blk)
args=defaults.update(args)
o = Object.new
args.each do |key,value|
o.class.send :define_method, key, proc { value }
end
o.instance_eval &blk
end

def announce(args)
parameters(args,:age=>21) {
“My name is #{first} #{last} and my age is #{age}”
}
end

announce(:first=>‘John’, :last=>‘Smith’)

Note that in the block passed to the method parameters you can only
access the args (first, last, …), but not instance variables or
methods from the calling object.

HTH

Regards,
Pit

Hi –

···

On Mon, 15 Dec 2003, Gregory Millam wrote:

def announce(args)
parameters(args,:age=>21)
“My name is #{first} #{last} and my age is #{age}”
end

announce(:first=>‘John’, :last=>‘Smith’) generates
“My name is John Smith and my age is 21”

You can use:

args.each { |key,val| eval “#{key}=#{val}” }

The variables you create that way will only be in scope in the
eval block itself:

eval “a = 1; puts a” # 1
puts a # error: a is undefined

David


David A. Black
dblack@wobblini.net

Pit Capitain wrote:

Note that in the block passed to the method parameters you can only
access the args (first, last, …), but not instance variables or
methods from the calling object.

This wasn’t correct: you can even call methods, just not access instance
variables.

Regards,
Pit

Received: Mon, 15 Dec 2003 11:38:30 +0900
And lo David wrote:

The variables you create that way will only be in scope in the
eval block itself:

eval “a = 1; puts a” # 1
puts a # error: a is undefined

David

Not quite; eval creates variables at current scope, But you are right about one thing - the .each |key,val| scope will keep all the variables.

str = “”
args.each do |key,val|
str << “#{key}=‘#{val}’\n”
end
eval str # Will place all the variables at the current scope.

I still recommend using the regexp search-and-replace instead of that, though. It also doesn’t require the string itself to be in the templating function. Not to mention all the ickiness involved with eval, etc.

Hi –

Received: Mon, 15 Dec 2003 11:38:30 +0900
And lo David wrote:

The variables you create that way will only be in scope in the
eval block itself:

eval “a = 1; puts a” # 1
puts a # error: a is undefined

David

Not quite; eval creates variables at current scope, But you are

No it doesn’t. Well, strictly speaking it does, since “current
scope” means its own block, which it then exits causing the
variables to go out of scope. But if by “current scope” you mean
the scope from which eval was called, then, as my example shows,
the variables are not created in that scope.

Your example shows the same thing actually :slight_smile:

$ cat ev.rb
args = { “a” => “hello” }
str = “”
args.each do |key,val|
str << “#{key}=‘#{val}’\n”
end
eval str
puts a

$ ruby ev.rb
ev.rb:8: undefined local variable or method `a’
for main:Object (NameError)

David

···

On Mon, 15 Dec 2003, Gregory Millam wrote:


David A. Black
dblack@wobblini.net

No it doesn't. Well, strictly speaking it does, since "current
scope" means its own block, which it then exits causing the
variables to go out of scope. But if by "current scope" you mean
the scope from which eval was called, then, as my example shows,
the variables are not created in that scope.

not understood, sorry

svg% cat b.rb
#!/usr/bin/ruby
args = { "a" => "hello" }
str = ""
args.each do |key,val|
   str << "#{key}='#{val}'\n"
end
eval str
eval 'puts a'
svg%

svg% b.rb
hello
svg%

Guy Decoux

Hi –

No it doesn’t. Well, strictly speaking it does, since “current
scope” means its own block, which it then exits causing the
variables to go out of scope. But if by “current scope” you mean
the scope from which eval was called, then, as my example shows,
the variables are not created in that scope.

not understood, sorry

svg% cat b.rb
#!/usr/bin/ruby
args = { “a” => “hello” }
str = “”
args.each do |key,val|
str << “#{key}=‘#{val}’\n”
end
eval str
eval ‘puts a’
svg%

svg% b.rb
hello

I was saying that if you do

puts a

instead of

eval ‘puts a’

‘a’ will not be known. This was in response to Greg’s post.

Mind you, I forgot about the fact that you can re-eval ‘a’ and get
back the same ‘a’ – but here the goal was to create variables at the
scope surrounding the eval, not in the eval block itself.

David

···

On Mon, 15 Dec 2003, ts wrote:


David A. Black
dblack@wobblini.net