Acessar variables ​​programaticamente por nome em Ruby

Eu não tenho certeza se isso é possível em Ruby, mas espero que haja uma maneira fácil de fazer isso. Eu quero declarar uma variável e depois descobrir o nome da variável. Isto é, para este simples trecho:

foo = ["goo", "baz"] 

Como posso obter o nome da matriz (aqui, “foo”) de volta? Se é realmente possível, isso funciona em alguma variável (por exemplo, escalares, hashes, etc.)?

Edit: Aqui está o que eu estou basicamente tentando fazer. Eu estou escrevendo um servidor SOAP que envolve uma class com três variables ​​importantes, e o código de validação é essencialmente este:

  [foo, goo, bar].each { |param| if param.class != Array puts "param_name wasn't an Array. It was a/an #{param.class}" return "Error: param_name wasn't an Array" end } 

Minha pergunta é: Posso replace as instâncias de “param_name” por foo, goo ou bar? Esses objects são todos Arrays, então as respostas que recebi até agora não parecem funcionar (com exceção da reengenharia da resposta da ala dbr )

E se você virar o seu problema? Em vez de tentar obter nomes de variables, obtenha as variables ​​dos nomes:

 ["foo", "goo", "bar"].each { |param_name| param = eval(param_name) if param.class != Array puts "#{param_name} wasn't an Array. It was a/an #{param.class}" return "Error: #{param_name} wasn't an Array" end } 

Se houvesse uma chance de uma das variables ​​não ser definida (ao invés de não ser uma matriz), você iria querer adicionar “rescue nil” ao final da linha “param = …” para manter o valor eval. de jogar uma exceção …

Você precisa reestruturar sua solução. Mesmo se você pudesse fazer isso (você não pode), a questão simplesmente não tem uma resposta razoável.

Imagine um método get_name.

 a = 1 get_name(a) 

Todos provavelmente concordariam que isso deveria retornar ‘a’

 b = a get_name(b) 

Deveria retornar ‘b’ ou ‘a’ ou um array contendo ambos?

 [b,a].each do |arg| get_name(arg) end 

Deve retornar ‘arg’, ‘b’ ou ‘a’?

 def do_stuff( arg ) get_name(arg) do do_stuff(b) 

Deveria devolver ‘arg’, ‘b’ ou ‘a’, ou talvez a matriz de todos eles? Mesmo se ele retornasse uma matriz, qual seria a ordem e como eu saberia interpretar os resultados?

A resposta para todas as perguntas acima é “Depende da coisa específica que eu quero no momento”. Não tenho certeza de como você poderia resolver esse problema para o Ruby.

Parece que você está tentando resolver um problema que tem uma solução muito mais fácil.

Por que não apenas armazenar os dados em um hash? Se você fizer..

 data_container = {'foo' => ['goo', 'baz']} 

..é, então, totalmente trivial para obter o nome ‘foo’.

Dito isto, você não deu nenhum contexto ao problema, então pode haver uma razão pela qual você não pode fazer isso.

[editar] Depois de esclarecer, eu vejo o problema, mas eu não acho que este é o problema .. Com [foo, bar, bla], é equivalente a dizer ['content 1', 'content 2', 'etc'] . O nome real das variables ​​é (ou melhor, deveria ser) totalmente irrelevante. Se o nome da variável é importante, é exatamente por isso que existem hashes.

O problema não é com iterar sobre [foo, bar] etc, é o problema fundamental de como o servidor SOAP está returando os dados e / ou como você está tentando usá-los.

A solução, eu diria, é fazer com que o servidor SOAP retorne hashes, ou, já que você sabe que sempre haverá três elementos, você não pode fazer algo como …

 {"foo" => foo, "goo" => goo, "bar"=>bar}.each do |param_name, param| if param.class != Array puts "#{param_name} wasn't an Array. It was a/an #{param.class}" puts "Error: #{param_name} wasn't an Array" end end 

OK, também funciona em methods de instância e, com base em seu requisito específico (aquele que você colocou no comentário), você poderia fazer isso:

 local_variables.each do |var| puts var if (eval(var).class != Fixnum) end 

Basta replace o Fixnum pela sua verificação de tipo específica.

Eu não sei de qualquer maneira de obter um nome de variável local. Mas, você pode usar o método instance_variables , isso retornará uma matriz de todos os nomes de variável de instância no object.

Chamada simples:

 object.instance_variables 

ou

 self.instance_variables 

para obter uma matriz de todos os nomes de variables ​​de instância.

Com base no joshmsmoore , algo como isso provavelmente faria isso:

 # Returns the first instance variable whose value == x # Returns nil if no name maps to the given value def instance_variable_name_for(x) self.instance_variables.find do |var| x == self.instance_variable_get(var) end end 

Kernel::local_variables , mas não tenho certeza se isso funcionará para as vars locais de um método, e não sei se você pode manipulá-lo de forma a fazer o que você deseja alcançar.

Ótima pergunta. Eu compreendo perfeitamente sua motivação. Deixe-me começar observando que existem certos tipos de objects especiais que, sob certas circunstâncias, têm conhecimento da variável à qual foram atribuídos. Esses objects especiais são, por exemplo. Instâncias de Module , instâncias de Class e instâncias de estrutura:

 Dog = Class.new Dog.name # Dog 

O problema é que isso funciona apenas quando a variável, para a qual a atribuição é executada, é uma constante. (Todos sabemos que as constantes do Ruby não são nada mais do que variables ​​emocionalmente sensíveis).

 x = Module.new # creating an anonymous module x.name #=> nil # the module does not know that it has been assigned to x Animal = x # but will notice once we assign it to a constant x.name #=> "Animal" 

Esse comportamento dos objects, que estão cientes de quais variables ​​foram atribuídas, é comumente chamado de magia constante (porque é limitado a constantes). Mas esta magia constante altamente desejável só funciona para certos objects:

 Rover = Dog.new Rover.name #=> raises NoMethodError 

Felizmente, eu escrevi uma gem y_support/name_magic , que cuida disso para você:

  # first, gem install y_support require 'y_support/name_magic' class Cat include NameMagic end 

O fato de que isso só funciona com constantes (isto é, variables ​​começando com uma letra maiúscula) não é uma limitação tão grande. De fato, dá liberdade para nomear ou não seus nomes à vontade:

 tmp = Cat.new # nameless kitty tmp.name #=> nil Josie = tmp # by assigning to a constant, we name the kitty Josie tmp.name #=> :Josie 

Infelizmente, isso não funcionará com literais de array, porque eles são construídos internamente sem usar o método #new , no qual o NameMagic baseia. Portanto, para conseguir o que você quer, você terá que subclassificar Array :

 require 'y_support/name_magic' class MyArr < Array include NameMagic end foo = MyArr.new ["goo", "baz"] # not named yet foo.name #=> nil Foo = foo # but assignment to a constant is noticed foo.name #=> :Foo # You can even list the instances MyArr.instances #=> [["goo", "baz"]] MyArr.instance_names #=> [:Foo] # Get an instance by name: MyArr.instance "Foo" #=> ["goo", "baz"] MyArr.instance :Foo #=> ["goo", "baz"] # Rename it: Foo.name = "Quux" Foo.name #=> :Quux # Or forget the name again: MyArr.forget :Quux Foo.name #=> nil # In addition, you can name the object upon creation even without assignment u = MyArr.new [1, 2], name: :Pair u.name #=> :Pair v = MyArr.new [1, 2, 3], ɴ: :Trinity v.name #=> :Trinity 

Consegui o comportamento constante de imitação de magia, pesquisando todas as constantes em todos os namespaces do espaço de object atual do Ruby. Isso desperdiça uma fração de segundo, mas como a pesquisa é executada apenas uma vez, não há penalidade de desempenho quando o object descobre seu nome. No futuro, a equipe principal do Ruby prometeu o gancho const_assigned .

Você não pode, você precisa voltar para a prancheta e reprojetar sua solução.

Foo é apenas um local para manter um ponteiro para os dados. Os dados não têm conhecimento do que aponta para isso. Nos sistemas Smalltalk, você poderia perguntar a VM por todos os pointers para um object, mas isso só lhe daria o object que continha a variável foo, e não o próprio foo. Não há uma maneira real de fazer referência a uma variável em Ruby. Como mencionado por uma resposta, você pode colocar uma tag nos dados que referenciam de onde ela veio ou algo semelhante, mas geralmente isso não é uma boa abordagem para a maioria dos problemas. Você pode usar um hash para receber os valores em primeiro lugar, ou usar um hash para passar para o seu loop, para que você saiba o nome do argumento para fins de validação, como na resposta do DBR.

A coisa mais próxima de uma resposta real à sua pergunta é usar o método Enumerable each_with_index em vez de cada, assim:

 my_array = [foo, baz, bar] my_array.each_with_index do |item, index| if item.class != Array puts "#{my_array[index]} wasn't an Array. It was a/an #{item.class}" end end 

Eu removi a declaração de retorno do bloco que você estava passando para each / each_with_index porque não significou nada. Cada um e each_with_index retornam a matriz em que estavam operando.

Há também algo sobre escopo em blocos que vale a pena notar aqui: se você definiu uma variável fora do bloco, ela estará disponível dentro dela. Em outras palavras, você poderia se referir a foo, bar e baz diretamente dentro do bloco. O inverso não é verdadeiro: variables ​​que você cria pela primeira vez dentro do bloco não estarão disponíveis fora dele.

Por fim, a syntax do / end é preferida para blocos de várias linhas, mas isso é simplesmente uma questão de estilo, embora seja universal no código ruby ​​de qualquer safra recente.