linkyndy
linkyndy

Reputation: 17920

Dynamically resolve class to global scope

I need to save a reference of a class to a hash:

@hash['foo'] = bar if bar.is_a?(Class)

The above piece of code lives in my /lib directory, it is not autoloaded every time, and bar is usually an autoloaded class. In order to avoid getting the "A copy of xxx has been removed from the module tree but is still active" error when I reload! my code, I am trying to resolve bar to the global scope, i.e.: add :: before the class name (Baz is becoming ::Baz).

I am not sure how I can dynamically perform this without converting the class to a string, prepending ::, and then converting it back to a class.

Upvotes: 2

Views: 236

Answers (1)

Stefan
Stefan

Reputation: 114218

When assigning a constant to a hash, the constant is resolved upon assignment: (not hash specific, this is just how constants work)

hash = {}
A = 1
hash[:a] = A
#=> 1          # <- 1 is being assigned, not A

A = 2
hash[:a]
#=> 1

One way to solve it is to store the constant's name:

hash = {}
A = 1
hash[:a] = 'A'
#=> 'A'

and to resolve it via const_get / constantize:

A = 2
Object.const_get(hash[:a])
#=> 2

This also works for nested constants:

hash[:pi] = 'Math::PI'
Object.const_get(hash[:pi])
#=> 3.141592653589793

If your object happens to be a named class (or module), you can retrieve its name via Module#name:

hash[:lazy_enum] = Enumerator::Lazy.name
#=> "Enumerator::Lazy"

Object.const_get(hash[:lazy_enum])
#=> Enumerator::Lazy

A different approach is to use a proc which references the constant in its block:

hash = {}
A = 1
hash[:a] = -> { A }
#=> #<Proc:0x00007fc4ba05f510@(irb):10 (lambda)>

the constant will be resolved when invoking the block:

A = 2
hash[:a].call
#=> 2

Upvotes: 5

Related Questions