Q: How to convert hashed parameters to local variables?

D

Dennis Misener

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

Gregory Millam

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
 
J

Jamis Buck

Dennis said:
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}"...
 
G

Gregory Millam

Received: Mon, 15 Dec 2003 11:38:30 +0900
And said:
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.
 
R

Robert Klemme

Dennis Misener said:
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
 
P

Pit Capitain

Dennis said:
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
 
P

Pit Capitain

Pit said:
...

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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top