Access variables programmatically by name in Ruby

后端 未结 11 807
没有蜡笔的小新
没有蜡笔的小新 2020-11-27 19:15

I\'m not entirely sure if this is possible in Ruby, but hopefully there\'s an easy way to do this. I want to declare a variable and later find out the name of the variable.

相关标签:
11条回答
  • 2020-11-27 19:27

    OK, it DOES work in instance methods, too, and, based on your specific requirement (the one you put in the comment), you could do this:

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

    Just replace Fixnum with your specific type checking.

    0 讨论(0)
  • 2020-11-27 19:28

    You can't, you need to go back to the drawing board and re-engineer your solution.

    0 讨论(0)
  • 2020-11-27 19:31

    I do not know of any way to get a local variable name. But, you can use the instance_variables method, this will return an array of all the instance variable names in the object.

    Simple call:

    object.instance_variables
    

    or

    self.instance_variables
    

    to get an array of all instance variable names.

    0 讨论(0)
  • 2020-11-27 19:35

    What if you turn your problem around? Instead of trying to get names from variables, get the variables from the names:

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

    If there were a chance of one the variables not being defined at all (as opposed to not being an array), you would want to add "rescue nil" to the end of the "param = ..." line to keep the eval from throwing an exception...

    0 讨论(0)
  • 2020-11-27 19:36

    There's Kernel::local_variables, but I'm not sure that this will work for a method's local vars, and I don't know that you can manipulate it in such a way as to do what you wish to acheive.

    0 讨论(0)
  • 2020-11-27 19:38

    Great question. I fully understand your motivation. Let me start by noting, that there are certain kinds of special objects, that, under certain circumstances, have knowledge of the variable, to which they have been assigned. These special objects are eg. Module instances, Class instances and Struct instances:

    Dog = Class.new
    Dog.name # Dog
    

    The catch is, that this works only when the variable, to which the assignment is performed, is a constant. (We all know that Ruby constants are nothing more than emotionally sensitive variables.) Thus:

    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"
    

    This behavior of objects being aware to which variables they have been assigned, is commonly called constant magic (because it is limited to constants). But this highly desirable constant magic only works for certain objects:

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

    Fortunately, I have written a gem y_support/name_magic, that takes care of this for you:

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

    The fact, that this only works with constants (ie. variables starting with a capital letter) is not such a big limitation. In fact, it gives you freedom to name or not to name your objects at will:

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

    Unfortunately, this will not work with array literals, because they are internally constructed without using #new method, on which NameMagic relies. Therefore, to achieve what you want to, you will have to subclass 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
    

    I achieved the constant magic-imitating behavior by searching all the constants in all the namespaces of the current Ruby object space. This wastes a fraction of second, but since the search is performed only once, there is no performance penalty once the object figures out its name. In the future, Ruby core team has promised const_assigned hook.

    0 讨论(0)
提交回复
热议问题