Подтвердить что ты не робот

Динамически задавать локальные переменные в Ruby

Мне интересно динамически устанавливать локальные переменные в Ruby. Не создавать методы, константы или переменные экземпляра.

Так что-то вроде:

args[:a] = 1
args.each_pair do |k,v|
  Object.make_instance_var k,v
end
puts a
> 1

Я хочу локальные переменные именно потому, что этот метод находится в модели, и я не хочу загрязнять глобальное или пространство объектов.

4b9b3361

Ответ 1

Проблема заключается в том, что блок внутри each_pair имеет разную область. Любые локальные переменные, назначенные в нем, будут доступны только там. Например, это:

args = {}
args[:a] = 1
args[:b] = 2

args.each_pair do |k,v|
  key = k.to_s
  eval('key = v')
  eval('puts key')
end

puts a

Производит следующее:

1
2
undefined local variable or method `a' for main:Object (NameError)

Чтобы обойти это, вы можете создать локальный хеш, назначить ключи для этого хэша и получить к ним доступ так:

args = {}
args[:a] = 1
args[:b] = 2

localHash = {}
args.each_pair do |k,v|
  key = k.to_s
  localHash[key] = v
end

puts localHash['a']
puts localHash['b']

Конечно, в этом примере он просто копирует исходный хэш со строками для ключей. Я предполагаю, что фактический прецедент, однако, более сложный.

Ответ 2

В качестве дополнительной информации для будущих читателей, начиная с ruby ​​2.1.0, вы можете использовать binding.local_variable_get и binding.local_variable_set:

def foo
  a = 1
  b = binding
  b.local_variable_set(:a, 2) # set existing local variable `a'
  b.local_variable_set(:c, 3) # create new local variable `c'
                              # `c' exists only in binding.
  b.local_variable_get(:a) #=> 2
  b.local_variable_get(:c) #=> 3
  p a #=> 2
  p c #=> NameError
end

Как указано в документе, это похоже на поведение

binding.eval("#{symbol} = #{obj}")
binding.eval("#{symbol}")

Ответ 3

интересно, вы можете изменить локальную переменную, но вы не можете установить:

def test
  x=3
  eval('x=7;')
  puts x
end

test = > 7

def test
  eval('x=7;')
  puts x
end

test = > NameError: undefined локальная переменная или метод `x 'для main: Object

Это единственная причина, по которой работает код Dorkus Prime.

Ответ 4

Я предлагаю вам использовать хэш (но продолжайте читать для других альтернатив).

Почему?

Разрешение произвольных именованных аргументов делает чрезвычайно неустойчивый код.

Скажем, у вас есть метод foo, который вы хотите принять эти теоретические именованные аргументы.

Сценарии:

  • Вызываемый метод (foo) должен вызвать частный метод (пусть его вызывает bar), который не принимает аргументов. Если вы передадите аргумент foo, который хотите сохранить в локальной переменной bar, он будет маскировать метод bar. Обходной путь состоит в том, чтобы иметь явные скобки при вызове bar.

  • Скажем, foo код присваивает локальную переменную. Но затем вызывающий решает передать в arg с тем же именем, что и локальная переменная. Назначение будет clobber аргумент.

В принципе, вызывающий метод никогда не сможет изменить логику метода.

Альтернативы

Альтернативная средняя земля включает OpenStruct. Это менее типизируется, чем использование хэша.

require 'ostruct'
os = OpenStruct.new(:a => 1, :b => 2)
os.a  # => 1
os.a = 2  # settable
os.foo  # => nil

Обратите внимание, что OpenStruct позволяет вам получить доступ к несуществующим членам - он вернет nil. Если вы хотите более строгую версию, используйте Struct вместо этого.

Это создает анонимный класс, а затем создает экземпляр класса.

h = {:a=>1, :b=>2}
obj = Struct.new(* h.keys).new(* h.values)
obj.a  # => 1
obj.a = 2  # settable
obj.foo  # NoMethodError

Ответ 5

так как вы не хотите констант

args = {}
args[:a] = 1
args[:b] = 2

args.each_pair{|k,v|eval "@#{k}=#{v};"}

puts @b

2

вы можете найти этот подход интересным (оцените переменные в правильном контексте)

fn="b*b"
vars=""
args.each_pair{|k,v|vars+="#{k}=#{v};"}
eval vars + fn

4