Define a constant in dynamically defined class

Have me a conundrum.

 k = Class.new() do
   class X; end
 end

k::X.object_id

results in

(irb):7: warning: toplevel constant X referenced by #<Class:

0x00000001954380>::X

I don’t want X to be toplevel. I want it to be under k. To
complicate matters I don’t have control over the block, as it actually
comes from a testing procedure defined by an end user. The actual code
is this:

  # Create a sub-case.
  #
   def context(desc=nil, &block)
    cls = Class.new(TestCase, &block)
    cls.desc(desc) if desc
    cls
  end

Is there any way to isolate X to k? And then apply it to the more
general dynamic case?

Thanks.

You could do this:

k = Class.new do
class_eval(“class X; end”)
end

puts k::X.object_id

On Jul 5, 10:45am, Mike B. [email protected] wrote:

You could do this:

k = Class.new do
class_eval(“class X; end”)
end

puts k::X.object_id

Thanks, but unfortunately that only works b/c it’s evaluating a string
and not a block.

I tried this thinking it should do the trick:

Class.new do
instance_eval(&block)
end

But no go :frowning:

Thomas S. wrote in post #1009163:

Have me a conundrum.

 k = Class.new() do
   class X; end
 end

k::X.object_id

results in

(irb):7: warning: toplevel constant X referenced by #<Class:

0x00000001954380>::X

I don’t want X to be toplevel. I want it to be under k.

As I understand it, constants are scoped statically, i.e. at parse time,
unless you force them to be dynamically looked up (such as self::X)

To
complicate matters I don’t have control over the block, as it actually
comes from a testing procedure defined by an end user.

If the user is doing something like this:

your_code {
class X; end
}

then I believe they are explicitly requesting X to be created at the top
level, or whatever module they are lexically within, e.g.

module Foo
your_code {
class X; end # note
}
end

[note] is parsed as Foo::X, and I don’t think you can override this,
because the block is build before your code is called.

string eval is fine because you’re parsing the code from scratch, rather
than taking an already-built block.

def context(&block)
before_constants = self.class.constants
k = Class.new(&block)
after_constants = self.class.constants

new_constants = after_constants - before_constants

new_constants.each do |const|
k.const_set(const, k.const_get(const) )
end

Object.class_eval do
new_constants.each do |const|
remove_const(const)
end
end

k::X.greet
puts k::PI
puts Object::X
end

context do
PI = 3.14

class X
def self.greet
puts ‘hello’
end
end

end

–output:–
hello
3.14
ruby.rb:20:in context': uninitialized constant X (NameError) from ruby.rb:23:in

Entering the block scope does not automatically change the current
context for constant definition (the core developers call it the
`cref’), nor does instance_eval and class_eval.

In other words, blocks don’t change the scope, and that is why you can
do things like this:

x = 10

[1,2,3].each do |num|
puts num * x
end

–output:–
10
20
30

Or:

x = 10

Object.class_eval do
define_method(‘show’) do
puts x
end
end

show()

–output:–
10

On the other hand, the keywords ‘class’, ‘module’, and ‘def’ create new
scopes, and in the case of ‘class’ and ‘module’ they also create new
namespaces.

On 7/5/2011 10:15 AM, Intransition wrote:

0x00000001954380>::X

[…]

Is there any way to isolate X to k? And then apply it to the more
general dynamic case?

Thanks.

You can use Module#const_set.

const_set :X, Class.new

Entering the block scope does not automatically change the current
context for constant definition (the core developers call it the
`cref’), nor does instance_eval and class_eval.

7stud – wrote in post #1009251:

context do
PI = 3.14

class X
def self.greet
puts ‘hello’
end
end

end

That is the code that is causing the problem. If the user writes that
code, then the “current class” when X is parsed is Object(i.e. its the
same “current class” that exists outside the block). Furthermore,
class definitions nest inside the current class/module.

For constant lookups, it doesn’t matter what the current class is when
the the “do block” actually executes.

Whoops. Let’s try that again:

def context(&block)
before_constants = self.class.constants
k = Class.new(&block)
after_constants = self.class.constants

new_constants = after_constants - before_constants

Object.class_eval do
new_constants.each do |const|
k.const_set(const, const_get(const) )
remove_const(const) #private method, so need self=Object
end
end

k::X.greet
puts k::PI
puts Object::X
end

context do
PI = 3.14

class X
def self.greet
puts ‘hello’
end
end

end

–output:–
hello
3.14
ruby.rb:17:in context': uninitialized constant X (NameError) from ruby.rb:20:in

On Jul 5, 7:57pm, Su Zhang [email protected] wrote:

Entering the block scope does not automatically change the current
context for constant definition (the core developers call it the
`cref’), nor does instance_eval and class_eval.

I’ve been reading up on this[1]. Looks like the was a period (1.9.1)
when it didn’t work this way. I am inclined to think it a bug. At the
very least there has to be work around.

[1]http://jfire.posterous.com/constant-lookup-in-ruby