Reputation: 1168
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
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:
#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
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