我有一个变量var = "some_name"
,我想创建一个新对象并将其分配给some_name
。我该怎么做? E.g。
var = "some_name"
some_name = Struct.new(:name) # I need this
a = some_name.new('blah') # so that I can do this.
答案 0 :(得分:34)
你不能在Ruby 1.9+中动态创建局部变量(你可以通过eval
在Ruby 1.8中):
eval 'foo = "bar"'
foo # NameError: undefined local variable or method `foo' for main:Object
它们可以在评估代码中使用,但是:
eval 'foo = "bar"; foo + "baz"'
#=> "barbaz"
Ruby 2.1添加了local_variable_set
,但这也无法创建新的局部变量:
binding.local_variable_set :foo, 'bar'
foo # NameError: undefined local variable or method `foo' for main:Object
如果不修改Ruby本身,就无法更改此行为。另一种方法是考虑将数据存储在另一个数据结构中,例如哈希,而不是许多局部变量:
hash = {}
hash[:my_var] = :foo
请注意,eval
和local_variable_set
都允许重新分配现有的本地变量:
foo = nil
eval 'foo = "bar"'
foo #=> "bar"
binding.local_variable_set :foo, 'baz'
foo #=> "baz"
答案 1 :(得分:5)
说到ruby 2.2.x,你确实不能在当前的上下文/绑定中以编程方式创建局部变量..但是你可以在一个特定的绑定中设置变量你有句柄。
b = binding
b.local_variable_set :gaga, 5
b.eval "gaga"
=> 5
有趣的是,每次调用binding
都会给你一个新的绑定。因此,您需要获得您感兴趣的绑定的句柄,然后在设置了所需的变量后在其上下文中进行评估。
这有用吗?例如,我想评估ERB,如果你可以使用<%= myvar %>
而不是<%= opts[:myvar] %>
或类似的东西,写ERB会更好。
要创建一个新的绑定,我正在使用模块类方法(我确定有人会纠正我如何正确地调用它,在java中我称之为静态方法)得到一个特殊变量集的干净绑定:
module M
def self.clean_binding
binding
end
def self.binding_from_hash(**vars)
b = self.clean_binding
vars.each do |k, v|
b.local_variable_set k.to_sym, v
end
return b
end
end
my_nice_binding = M.binding_from_hash(a: 5, **other_opts)
现在您只拥有所需变量的绑定。您可以使用它来更好地控制ERB或其他(可能是第三方)可信代码的评估(这是不任何类型的沙箱)。这就像定义一个界面。
更新:关于绑定的一些附加说明。创建它们的位置也会影响方法和常量分辨率的可用性。在上面的例子中,我创建了一个相当干净的绑定。但是如果我想提供某些对象的实例方法,我可以通过类似的方法创建一个绑定,但是在该对象的类中。 e.g。
module MyRooTModule
class Work
def my_instance_method
...
end
def not_so_clean_binding
binding
end
end
class SomeOtherClass
end
end
现在我的my_object.not_so_clean_binding
将允许代码在#my_instance_method
对象上调用my_object
。以同样的方式,您可以使用此绑定而不是SomeOtherClass.new
在代码中调用示例MyRootModule::SomeOtherClass.new
。因此,在创建绑定时,有时需要更多考虑而不仅仅是局部变量。 HTH
答案 2 :(得分:2)
虽然正如其他人所指出的那样,你不能在Ruby中动态创建局部变量,但你可以使用方法在某种程度上模拟这种行为:
hash_of_variables = {var1: "Value 1", var2: "Value 2"}
hash_of_variables.each do |var, val|
define_method(var) do
instance_variable_get("@__#{var}")
end
instance_variable_set("@__#{var}", val)
end
puts var1
puts var2
var1 = var2.upcase
puts var1
打印:
Value 1
Value 2
VALUE 2
有些库将此技术与instance_exec
结合使用,以显示块内的局部变量:
def with_vars(vars_hash, &block)
scope = Object.new
vars_hash.each do |var, val|
scope.send(:define_singleton_method, var) do
scope.instance_variable_get("@__#{var}")
end
scope.instance_variable_set("@__#{var}", val)
end
scope.instance_exec(&block)
end
with_vars(a: 1, b:2) do
puts a + b
end
打印:3
请注意,抽象绝不是完美的:
with_vars(a: 1, b:2) do
a = a + 1
puts a
end
结果:undefined method `+' for nil:NilClass
。这是因为a=
定义了一个实际的局部变量,初始化为nil
,该变量优先于方法a
。然后调用a.+(1)
,nil
没有+
方法,因此会抛出错误。
因此,虽然此方法对于模拟只读局部变量非常有用,但当您尝试在块内重新分配变量时,它并不总是有效。
答案 3 :(得分:2)
其他人写道,你无法在本地环境中动态声明 true 变量。但是,您可以使用对象属性实现类似的功能,因为在Ruby世界中,一切都是对象(甚至是主要上下文),您可以使用新属性轻松扩展这些对象。对于corse,这个操作可以动态完成。让我们研究一下这种方法。
首先,让我们使用irb
查看主要范围。
> self
=> main
> self.class
=> Object
> self.class.ancestors
=> [Object, Kernel, BasicObject]
正如您现在所看到的,main
确实是一个对象。对象可以具有与变量具有相同indirection 属性的属性。通常,在声明新类时,我们将使用attr_accessor
方法,但main
已经是实例化对象,因此我们无法直接声明新属性。这里模块mixins 来救援。
variable_name = 'foo'
variable_value = 'bar'
variable_module = Module.new do
attr_accessor variable_name.to_sym
end
include variable_module
instance_variable_set("@#{variable_name}", variable_value)
p foo # "bar"
self.foo = 'bad'
p foo # "baz"
self.class.ancestors
# [Object, #<Module:0x007f86cc073aa0>, Kernel, BasicObject]
现在您看到main
对象被引入新属性foo
的新模块所污染。如需进一步检查,您可以methods
运行main
现在还有两个方法foo
和foo=
。
为简化此操作,我写了metaxa gem,我强烈建议您查看。这是如何使用它的示例。
require 'metaxa'
include Metaxa
introduce :foo, with_value: 'foo'
puts foo == 'foo' # true
puts foo === get(:foo) # true
set :foo, 'foobar'
puts foo == 'foobar' # true
puts foo === get(:foo) # true
self.foo = 'foobarbaz'
puts foo == 'foobarbaz' # true
puts foo === get(:foo) # true