Метапрограммирование Ruby: имена переменных динамического экземпляра


96

Допустим, у меня есть следующий хеш:

{ :foo => 'bar', :baz => 'qux' }

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

class Example
  def initialize( hash )
    ... magic happens here...
  end
end

... так что внутри модели я получу следующее ...

@foo = 'bar'
@baz = 'qux'

?

Ответы:


170

Метод, который вы ищете instance_variable_set. Так:

hash.each { |name, value| instance_variable_set(name, value) }

Или, короче,

hash.each &method(:instance_variable_set)

Если в именах переменных вашего экземпляра отсутствует символ «@» (как в примере с OP), вам нужно будет добавить их, чтобы это было больше похоже на:

hash.each { |name, value| instance_variable_set("@#{name}", value) }

18
У меня не работало на 1.9.3. Я использовал это вместоhash.each {|k,v| instance_variable_set("@#{k}",v)}
Андрей

3
еще одна причина любить Руби
jschorr 02

не могли бы вы объяснить, как hash.each &method(:instance_variable_set)метод instance_variable_setполучает два необходимых параметра?
Арнольд Роа

есть идеи, как это сделать рекурсивно? (если во входном хэше несколько уровней)
nemenems

13
h = { :foo => 'bar', :baz => 'qux' }

o = Struct.new(*h.keys).new(*h.values)

o.baz
 => "qux" 
o.foo
 => "bar" 

1
Это довольно интересно ... что именно .new()делает вторая цепочка ?
Эндрю

3
@Andrew: Struct.newсоздает новый класс на основе ключей хеширования, а затем второй newсоздает первый объект только что созданного класса, инициализируя его значениями хеша. См ruby-doc.org/core-1.8.7/classes/Struct.html
DigitalRoss

2
На самом деле это действительно отличный способ сделать это, так как Struct в значительной степени предназначен для этого.
Чак

2
Или используйте OpenStruct . require 'ostruct'; h = {:foo => 'foo'}; o = OpenStruct.new(h); o.foo == 'foo'
Джастин Форс,

Мне пришлось сопоставить свои ключи с символами:Struct.new(*hash.keys.map { |str| str.to_sym }).new(*hash.values)
ошибка

8

Вы заставляете нас плакать :)

В любом случае смотрите Object#instance_variable_getи Object#instance_variable_set.

Удачного кодирования.


э-э да, я не мог не задаться вопросом ... почему? когда будет подходящее время для этого?
Зак Смит

например, мне может потребоваться общий set_entityобратный вызов для всех контроллеров, и я не хочу вмешиваться в существующие переменные экземпляраdef set_entity(name, model); instance_variable_set(name, model.find_by(params[:id])); end;
user1201917

5

Вы также можете использовать, sendкоторый запрещает пользователю устанавливать несуществующие переменные экземпляра:

def initialize(hash)
  hash.each { |key, value| send("#{key}=", value) }
end

Используйте, sendкогда в вашем классе есть сеттер, например, attr_accessorдля переменных вашего экземпляра:

class Example
  attr_accessor :foo, :baz
  def initialize(hash)
    hash.each { |key, value| send("#{key}=", value) }
  end
end
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.