Feature #8643

Add Binding.from_hash

Added by Rodrigo Rosenfeld Rosas 9 months ago. Updated 8 months ago.

[ruby-core:56041]
Status:Open
Priority:Normal
Assignee:Koichi Sasada
Category:-
Target version:-

Description

Binding.from_hash would work like:

class Binding
def self.from_hash(hash)
OpenStruct.new(hash){ binding }
end
end

It would simplify things like:

ERB.new(IO.read 'template.erb').result Binding.fromhash(templatelocal: 'example')

Or if you need to eval some code in another process (JRuby, for instance) and need to pass some arguments to the eval code in a hash form.

I didn't want to pollute Hash by adding Hash#tobinding. I believe Binding.fromhash is more appropriate.


Related issues

Related to ruby-trunk - Feature #8430: Rational number literal Closed 05/21/2013

History

#1 Updated by Rodrigo Rosenfeld Rosas 9 months ago

Whoops. I can't update the description. The implementation should be:

OpenStruct.new(hash).instance_eval { binding }

#2 Updated by Charlie Somerville 9 months ago

Personally I think hash keys should be local variables in the binding, not method calls against self.

It's hard to express this in Ruby, but this can easily be done from the C side.

#3 Updated by Charlie Somerville 9 months ago

PS: I'm neutral towards this feature. I've got no strong feelings that it should or shouldn't be part of Ruby.

#4 Updated by Rodrigo Rosenfeld Rosas 9 months ago

I don't mind on it being a local var in the binding since it should work either way, just an implementation detail I'd say... Maybe other Ruby implementations might prefer to use them as methods?

#5 Updated by Koichi Sasada 8 months ago

  • Assignee set to Koichi Sasada

What do you think about [Feature #8761]?

Usage:

def getemptybinding
binding
end
...
b = getemptybinding
hash.each{|k, v|
b.localvariableset(k, v)
}
# use b

I think that Binding#localvariableset() can be extended to accept one hash parameter (pairs of local variable name and value).

b = getemptybinding
b.localvariableset(hash)
b.localvariableset(a: 1, b: 2)

#6 Updated by Rodrigo Rosenfeld Rosas 8 months ago

I don't quite understand how that would help me, Koichi.

How could I use #8761 to get the same result as the example in the description?

ERB.new(IO.read 'template.erb').result Binding.fromhash(templatelocal: 'example')

My current alternative is:

ERB.new(IO.read 'template.erb').result OpenStruct.new(template_local: 'example'){ binding }

How would #8761 make it easier for me?

#7 Updated by Rodrigo Rosenfeld Rosas 8 months ago

In other words, I want an easier way to convert a hash to a binding. I first thought about Hash#to_binding but it didn't feel right to me...

#8 Updated by Koichi Sasada 8 months ago

(2013/08/09 22:10), rosenfeld (Rodrigo Rosenfeld Rosas) wrote:

In other words, I want an easier way to convert a hash to a binding. I first thought about Hash#to_binding but it didn't feel right to me...

Ok. Maybe I misunderstood your proposal.

What is conversion from Hash to Binding?
I think it is Binding has a local variables which specified pairs in Hash.

hash = {a: 1, b: 2}
b = Binding.to_hash(hash)
eval("p [a, b]", b) #=> [1, 2]

Could you explain what do you want?

--
// SASADA Koichi at atdot dot net

#9 Updated by Nobuyoshi Nakada 8 months ago

(13/08/09 23:34), SASADA Koichi wrote:

What is conversion from Hash to Binding?
I think it is Binding has a local variables which specified pairs in Hash.

hash = {a: 1, b: 2}
b = Binding.to_hash(hash)

Binding.from_hash ?

#10 Updated by Koichi Sasada 8 months ago

nobu (Nobuyoshi Nakada) wrote:

Binding.from_hash ?

Yes.

#11 Updated by Rodrigo Rosenfeld Rosas 8 months ago

Koichi-san, that's correct:

eval 'p a, b', Binding.from_hash(a: 1, b: 2) #=> 1, 2

#12 Updated by Rodrigo Rosenfeld Rosas 8 months ago

I didn't notice I replied only to Koichi Sasada when replying to the ruby-core list. Is it possible to set it up so that the reply-to field is set to ruby-core?

Here is some discussion from us from those e-mails so that everyone could have access to it:

(2013/08/10 20:47), Rodrigo Rosenfeld Rosas wrote:

I'm not sure how else to explain it

The only API example I know that requires a binding is the ERB one

It's designed to be used this way:

a = 1
b = 2
erb.result binding # both a and b are available inside the template as
well as other methods and variables, like erb
Koichi wrote:

Please try:

require 'erb'
bind = binding
bind.localvariableset(:a, 1)
bind.localvariableset(:b, 2)
puts ERB.new("<%= a %> and <%= b %>").result(bind)
#=> 1 and 2

That works, but it is too much trouble for a simple requirements and
besides that more methods and variables may leak when using the current
binding.

That's why people will often use the "OpenStruct.new(hash){binding}"
trick, since it's much shorter, but still a hack in my opinion.

This is often used in automation tools like Chef or Puppet where the
recipe settings (stored as a hash, usually) should be available for some
templates.

Suppose you have the NewRelic settings under settings[:newrelic] hash
(eg.: { application
name: 'My App', enable_rum: true })

In such cases, when generating the newrelic.yml from a newrelic.yml.erb
template, those tools would process it as:

File.write 'newrelic/location/newrelic.yml', ERB.new(File.read
'path/to/newrelic.yml.erb').result
OpenStruct.new(settings[:new_relic]){binding}

That's why I've created two tickets based on this common requirement. On
this specific one I'm suggesting a Binding.from_hash(hash) to make it
easier to get a binding from a hash for usage in such API's. The other
one suggested ERB to accept also a hash, instead of a binding for #result.

#13 Updated by Rodrigo Rosenfeld Rosas 8 months ago

Koichi then replied with:

I'm not sure what methods and variables are leaks.

For example, only "make_binding" mathod is leaked.

##
def makebinding(hash)
_
b = binding
hash.each{|k, v|
_b.localvariableset(k, v)
}
_
b
end

createdbinding = makebinding(a: 1, b: 2)
...

#14 Updated by Rodrigo Rosenfeld Rosas 8 months ago

For that tiny script, this is true, Koichi, but usually we get a binding from some class, so all methods would be available as well as other intermediary local variables in the method calling erb#results.

Like this:

class A
def a
1
end

def b
eval 'p a', binding
end
end
A.new.b # a has leaked

Also available in: Atom PDF