Kites
Kites

Reputation: 1168

How do I identify the variable's name as a string from self?

I wrote a wrapper for the Ruby OpenStruct library. Using this wrapper, a user declares a variable like so:

example = RStruct.new()

I would like to access the variable's name like so:

example.name # => "example"

This would prove useful when operating from within a class and calling self.name

I built the RStruct class, so adding the name method wouldn't be hard. I'm looking to find the user's variable from self.

If any language can do it, it's Ruby, right?

Upvotes: 2

Views: 160

Answers (2)

Boris Stitnicky
Boris Stitnicky

Reputation: 12578

This situation pops up in Ruby so often, that I have written a gem for it. What you normally would do in vanilla Ruby would be something like:

fred = Foobar.new( name: 'fred' )

Not only does "fred" repeat twice in the above line, but also you have to cater to the named parameter :fred, and you have to do chores such as writing RStruct#name method etc. If you gem install y_support, then you can write:

require 'y_support/name_magic'

class Foobar
  include NameMagic
end

And now:

Fred = Foobar.new
Fred.name #=> "Fred"

You are limited that only assignments to constants (starting with a capital letter) work to name a nameless object whose class includes NameMagic, but anyway "fred" should be capitalized. Ruby constants double up as capitalized proper names of things, and Ruby modules are sometimes also called namespaces. This is consistent with the behavior of naming classes by constant assignment, such as Animal = Class.new, also known as Ruby built-in constant magic.


APPENDIX DESCRIBING NAME MAGIC FEATURES IN MORE DETAIL:

NameMagic also provides a limited number of frequently used services related to naming. One that is often needed is the instance registry:

Foobar.instances
#=> [Fred]

The registry itself is a hash and can be accessed via:

Foobar.__instances__ # Don't modify this hash unless you know what you are doing.

A method much more useful than it seems is #instance method:

Foobar.instance "Fred"
#=> Fred
Foobar.instance Fred
#=> Fred

This method converts its parameter, be it a string, the object itself, or something else, into the corresponding object, and raises a NameError is there is no such instance.

Array is extended with #names method, so you can get the names of objects in an array easily (I confirmed to myself that this is an oft needed feature):

Foobar.instances.names
#=> ["Fred"]

Hash is extended with #keys_to_names method, so you can write { Fred => 42 }.keys_to_names to switch from using objects to using their names. This one is not needed so often, but I did include it, since naming is intimately connected to hashing, a name is a kind of a hash function that is supposed to be meaningful to and easily memorized by humans. So much for extensions to the core classes. Other methods you can look up in the documentation include Foobar.nameless_instances, Foobar.forget, Foobar.forget_nameless_instances, Foobar.forget_all_instances (to allow the disused objects to be GCed, that's just for advanced use and to allay hypothetical complaints about the gem eating up memory).

The current version of the gem breaks badly when you assign to a constant twice, which is already remedied in the next scheduled version, whose release is held back by a dependency (another gem of mine). Also, thanks for pointing me towards a way to suppress the annoying warnings generated whe one touches some obscure constants in the deep namespace.

The gem allows also naming by a more standard method, using :name parameter rather than constant assignment, so this is one more chore the gem user doesn't have to do:

Foobar.new name: "Joe"
Foobar.instances #=> [Fred, Joe]

Or using #name= setter.

x = Foobar.new
x.name = "Sam"
Foobar.instances #=> [Fred, Joe, Sam]

The final useful feature I want to introduce here are two hooks, #name_set_hook and #name_get_hook. The first one if very useful to validate and/or modify the suggested name. Also, there is a good chance you will need to execute some code not upon instantiation (because the instances using name_magic are born nameless), but only when the name is known, such as registering the object under its name into various collections of yours. #name_set_hook is the place to put that code in. This feature is a bit precarious, in the sense that you have to know the rules:


  1. The block is expected to be ternary, with ordered arguments name, instance and previous name.
  2. The block must return a valid name, which will be used instead of the suggested name. This gives one the opportunity to modify the name, but if you don't want to modify it, you still must return the unmodified version.
  3. The block code must refrain from directly or indirectly triggering an inquiry about the name so-far unnamed instance, or endless loop will ensue. In particular, #inspect and #to_s method must not be invoked for the nameless instance. Doing it anywhere else is OK, but not in this block.

Foobar.name_set_hook { |name, instance, old_name|
  puts "Name #{name} was requsted for object #{instance.object_id}." # not "#{instance}",
  # because "#{instance}" would invoke instance.to_s method.
  puts "Such name is not acceptable to our church."
  modified_name = "#{name}x"
  puts "The name will be #{modified_name}."
  # notify your tables that the object has just been named with modified_name
  acceptable_name # the block must return the name to be used, even if it doesn't modify it
}

Nathan = Foobar.new
#=> Name Nathan was requsted for object 76778240.
#=> Such name is not acceptable to our church.
#=> The name will be Nathanx.

Foobar.instances #=> [Fred, Joe, Sam, Nathanx]

Much less useful hook is #name_get_hook. It's only useful when you want the names have more than one alternative ways of presentation an you want to choose a particular one. For example, physical units can be held as all-caps constants, such as

class Unit; include NameMagic end
GRAM, METRE, SECOND = Unit.new, Unit.new, Unit.new
#=> [GRAM, METRE, SECOND]

But you might want them to present themselves in lower case:

Unit.name_get_hook { |name| name.downcase }
Unit.instances
#=> [gram, metre, second]

Method #instance recognizes both cases:

Unit.instance "metre"
#=> metre
Unit.instance "METRE"
#=> metre

But internally, the stored name is still the upper case version, which we can confirm by investigating the instance registry hash accessible via #__instances__ method.

Unit.__instances__[ Unit.instance( "metre" ) ]
#=> "METRE"

This was my actual use case that inspired me to add #name_get_hook feature, which is necessary because the names have to start with a capital letter, but the convention for units is to write them in all small letters. In other words, the hidden rule of #name_get_hook use is that there must be an unambiguous way to derive the actual name from the presented alternative form.

The gem's Github repo is https://github.com/boris-s/y_support. And now I should pull myself together and put this text into the README file lol.

Upvotes: 2

Jörg W Mittag
Jörg W Mittag

Reputation: 369438

Variables aren't objects. You can't do anything with them except assign to them and dereference them.

example.name

will always mean "dereference example and send the message name to the result of dereferencing example", it will never mean "send the message name to the variable example", because a) that would lead to an ambiguity, and b) variables aren't objects, so you can't send messages to them.

Upvotes: 1

Related Questions