Access variables programmatically by name in Ruby

后端 未结 11 914
没有蜡笔的小新
没有蜡笔的小新 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: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.

提交回复
热议问题