variáveis ​​programaticamente acessar por nome em Ruby

votos
24

Eu não estou totalmente certo se isso é possível em Ruby, mas espero que não há uma maneira fácil de fazer isso. Quero declarar uma variável e, mais tarde descobrir o nome da variável. Ou seja, para esta simples trecho:

foo = [goo, baz]

Como posso obter o nome do array (aqui, foo) de volta? Se é realmente possível, isso funciona em qualquer 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 classe com três variáveis ​​importantes, e o código de validação é essencialmente esta:

  [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
      }

A minha pergunta é então: Posso substituir as instâncias de 'PARAM_NAME' com foo, viscosidade, ou bar? Esses objetos são todos Arrays, então as respostas que recebi até agora não parecem funcionar (com exceção de engenharia re-a coisa toda ala resposta do DBR )

Publicado 12/09/2008 em 09:08
fonte usuário
Em outras línguas...                            


11 respostas

votos
40

E se você ligar o seu problema ao redor? Em vez de tentar obter nomes de variáveis, consiga as variáveis ​​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 possibilidade de uma das variáveis ​​não sendo definido em tudo (em oposição a não ser um array), você gostaria de adicionar "nil resgate" ao fim da linha "param = ..." para manter o eval de lançar uma exceção ...

Respondeu 15/09/2008 em 15:41
fonte usuário

votos
28

Você precisa re-arquitetar sua solução. Mesmo se você poderia fazê-lo (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)

Todo mundo poderia provavelmente concordam que este deve retornar 'a'

b = a
get_name(b)

Deve voltar 'b' ou 'a', ou um array contendo ambos?

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

Deve voltar 'arg', 'b' ou 'a'?

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

Deve voltar 'arg', 'b' ou 'a', ou talvez a matriz de todos eles? Mesmo que o fizesse retornar um array, o que seria o fim e como eu ia saber como interpretar os resultados?

A resposta para todas as perguntas acima é "Depende da coisa em particular que eu quero no momento." Eu não sei como você poderia resolver esse problema para Ruby.

Respondeu 15/09/2008 em 00:43
fonte usuário

votos
8

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

Porque não basta armazenar os dados em um hash? Se você fizer..

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

.it é então absolutamente trivial para obter o nome 'foo'.

Dito isto, você não tenha dado qualquer contexto para o problema, portanto, pode haver uma razão que você não pode fazer isso ..

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

O problema não é com a iteração sobre [foo, bar] etc, é o problema fundamental com a forma como o servidor SOAP é returing os dados, e / ou como você está tentando usá-lo.

A solução, eu diria, é que quer fazer os hashes de retorno do servidor SOAP, ou, uma vez que você sabe que há sempre vai ser 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
Respondeu 12/09/2008 em 13:53
fonte usuário

votos
5

OK, ela não funciona em métodos de instância, também, e, com base em sua exigência específica (o que você colocar no comentário), você poderia fazer isso:

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

Basta substituir Fixnumcom o seu verificação de tipo específico.

Respondeu 12/09/2008 em 13:35
fonte usuário

votos
2

Ótima pergunta. Compreendo perfeitamente a sua motivação. Deixe-me começar por referir, que há certos tipos de objetos especiais, que, sob certas circunstâncias, ter conhecimento da variável, à qual foram atribuídos. Esses objetos especiais são, por exemplo. Modulecasos, Classinstâncias e Structsituações:

Dog = Class.new
Dog.name # Dog

O problema é que isto funciona apenas quando a variável, a que a atribuição é realizada, é uma constante. (Nós todos sabemos que as constantes Ruby são nada mais do que as variáveis ​​emocionalmente sensíveis). Assim:

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"

Este comportamento de objetos sendo conscientes de quais variáveis que lhes foram atribuídas, é comumente chamado de magia constante (porque ele é limitado a constantes). Mas isso altamente desejável magia constante só funciona para certos objetos:

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

Felizmente, eu tenho escrito uma jóiay_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, que isso só funciona com constantes (ie. Variáveis ​​começando com uma letra maiúscula) não é uma grande limitação tal. Na verdade, dá-lhe a liberdade para nomear ou não nomear seus objetos à 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 vai funcionar com literais de matriz, porque eles são construídos internamente sem o uso de #newmétodo, no qual NameMagicse baseia. Portanto, para alcançar o que você quer, você terá que subclasse 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

Eu alcançado o comportamento magic-imitando constante por pesquisar todas as constantes em todos os espaços de nomes do atual espaço de objeto Ruby. Isto desperdiça uma fração de segundo, mas desde que a pesquisa é realizada apenas uma vez, não há nenhuma penalidade de desempenho uma vez que o objeto descobre seu nome. No futuro, Ruby equipe principal prometeu const_assignedgancho .

Respondeu 10/06/2013 em 18:28
fonte usuário

votos
2

Kernel::local_variables, mas eu não tenho certeza que isso vai funcionar para vars locais de um método, e eu não sei se você pode manipulá-lo de tal forma a fazer o que quiser para conseguir.

Respondeu 12/09/2008 em 12:53
fonte usuário

votos
2

Com base joshmsmoore , algo como isto provavelmente fazê-lo:

# 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
Respondeu 12/09/2008 em 12:35
fonte usuário

votos
2

Eu não sei de qualquer maneira de obter um nome de variável local. Mas, você pode usar o instance_variablesmétodo, este irá retornar uma matriz de todos os nomes de variáveis de instância no objeto.

simples chamada:

object.instance_variables

ou

self.instance_variables

para obter uma matriz de todos os nomes de variáveis ​​de instância.

Respondeu 12/09/2008 em 09:17
fonte usuário

votos
1

A coisa mais próxima de uma verdadeira resposta para você pergunta é usar o método each_with_index Enumerable em vez de cada um, 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 instrução de retorno do bloco que estavam passando a cada / each_with_index porque não fazer / dizer nada. Cada um e each_with_index tanto retornar a matriz em que eles estavam operando.

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

Finalmente, a sintaxe do / final é o preferido para os blocos de multi-linha, mas isso é simplesmente uma questão de estilo, embora seja universal em código Ruby de qualquer vindima recente.

Respondeu 16/09/2008 em 00:22
fonte usuário

votos
1

Foo só é um local para armazenar um ponteiro para os dados. Os dados não têm conhecimento do que aponta para ele. Em sistemas de Smalltalk você poderia pedir a VM para todos os ponteiros para um objeto, mas que só iria obter-lhe o objeto que continha a variável foo, não foo em si. Não há nenhuma maneira real para referenciar uma vaiable em Ruby. Como mencionado por uma resposta que você pode stil colocar uma etiqueta nos dados que faz referência de onde veio ou como, mas geralmente isso não é uma boa apporach a maioria dos problemas. Você pode usar um hash para receber os valores em primeiro lugar, ou usar um hash para passar para o loop para que você saiba o nome do argumento para fins de validação como na resposta do DBR.

Respondeu 15/09/2008 em 21:17
fonte usuário

votos
1

Você não pode, você precisa voltar à prancheta de desenho e re-engenharia sua solução.

Respondeu 12/09/2008 em 16:17
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more