I’m writing a DSL, and I want to use some constants. To be clean, I
don’t want to pollute the global constant space. To be tight, I also
don’t want the user to have to scope the constant using Foo::BAR, but
instead be able to use just BAR.
The following surprised me. As the scope of the block is an instance of
Foo, I had hoped/assumed that it would have access too Foo’s constants.
Alas, no.
class Foo
BAR = 1
def initialize( &block )
instance_eval &block
end
def bork
puts “bork: self is #{self}”
puts “bork: BAR is #{BAR}!”
end
end
Foo.new{
bork
puts “block: self is #{self}”
puts “block: BAR is #{BAR}!”
}
#=> bork: self is #Foo:0x32c808
#=> bork: BAR is 1!
#=> block: self is #Foo:0x32c808
#=> NameError: uninitialized constant BAR
For now, I’ll just shove my constants into global space before the
instance_eval, and remove them afterwards. Is there a better/cleaner
way to accomplish my goals?
For those interested in doing the same, I’ve hacked up a quick method
for promoting the constants of a class/module to the main Object scope,
and then restoring the original values.
You can:
Automatically promote all constants of the class/module
Promote only specific constants
Choose to explicitly restore the constants or
Supply a block that will be yielded to before the constants are
automatically restored
class Module
Push the constants of this class/module up to global space
saving any original values in Object for later restoration
If no names are supplied, all constants in this class/module
are promoted.
If a block is supplied, it will be yielded to before automatically
restoring the global scope to its pristine state.
def promote_constants( *const_names )
const_names.flatten!
const_names = self.constants if const_names.empty? @_original_constant_values = {}
const_names.each { |name|
if self.const_defined?( name )
if Object.const_defined?( name ) @_original_constant_values[ name ] = Object.const_get( name )
end
Object.const_set( name, self.const_get( name ) )
else
warn "Cannot promote non-existent constant '#{name}'"
end
}
if block_given?
yield
restore_constants
end
end
Restore Object’s constants to undo the effects of
#promote_constants
def restore_constants( *const_names )
return unless @_original_constant_values
const_names.flatten!
const_names = self.constants if const_names.empty?
const_names.each { |name|
if val=@_original_constant_values[ name ]
Object.const_set( name, val )
else
Object.instance_eval{
remove_const( name )
}
end
} @_original_constant_values = nil
end
end
class Foo
BAR = 1
def initialize( &block )
def initialize(str)
instance_eval &block
instance_eval str
end
def bork
puts “bork: self is #{self}”
puts “bork: BAR is #{BAR}!”
end
end
Foo.new{
Foo.new %Q{
bork
puts “block: self is #{self}” #puts “block: BAR is #{BAR}!”
p BAR
}
Now what do you get?
#=> bork: self is #Foo:0x32af44
#=> bork: BAR is 1!
#=> block: self is #Foo:0x32af44
#=> NameError: uninitialized constant BAR
I’m writing a DSL, and I want to use some constants. To be clean, I
don’t want to pollute the global constant space. To be tight, I also
don’t want the user to have to scope the constant using Foo::BAR, but
instead be able to use just BAR.
The following surprised me. As the scope of the block is an instance of
Foo, I had hoped/assumed that it would have access too Foo’s constants.
Alas, no.
class Foo
BAR = 1
def initialize( &block )
instance_eval &block
end
def bork
puts “bork: self is #{self}”
puts “bork: BAR is #{BAR}!”
end
end
Foo.new{
bork
puts “block: self is #{self}”
puts “block: BAR is #{BAR}!”
}
#=> bork: self is #Foo:0x32c808
#=> bork: BAR is 1!
#=> block: self is #Foo:0x32c808
#=> NameError: uninitialized constant BAR
For now, I’ll just shove my constants into global space before the
instance_eval, and remove them afterwards. Is there a better/cleaner
way to accomplish my goals?
Just a constant lookup issue for a closure. Doing
this would also work:
puts “Block: BAR is #{self.class.const_get ‘BAR’}”