Confusion with class_eval

class A
end
A.class_eval do
@@attr = 100
def self.get_attr
@@attr
end
def self.set_attr(_x)
p “hi”
@@attr = _x
end
end
p A.class_variable_get(:@@attr)
p Object.class_variable_get(:@@attr)

Output:

D:/Rubyscript/My ruby learning days/Scripts/TEST.RB:78: warning: class
variable access from toplevel
100
100

C:\Program Files\Notepad++>

My question is how @attr becomes Object class’s class variable?

Pritam D. wrote in post #1104053:

My question is how @attr becomes Object class’s class variable?

Not at my workstation to verify, but I’d suggest that this error:

D:/Rubyscript/My ruby learning days/Scripts/TEST.RB:78: warning: class
variable access from toplevel
is telling you that using a cvar inside #class_eval doesn’t do what you
expect it should. It appears to be searching for the cvar outside
the scope of class_eval (i.e. on the toplevel object, Object)

If I was at my workstation I’d use this to test:

class A
  def A.bar
    @@bar
  end
end
class B
  class << self
    def foo
      A.class_eval { @@bar = 1 }
    end
  end
  def B.bar
    @@bar
  end
end
p A.bar, B.bar
B.foo
p A.bar, B.bar

(Just made up that code, haven’t verified it in the slightest.)
I’d assume that A.bar would always return nil, and B.bar would return
nil and then 1.

Perhaps you should use:

A.class_eval do
  class_variable_set :@@attr, 100
  # etc.
end

(Again, I just made up that code, no idea if it’s correct.)

Pritam D. wrote in post #1104053

My question is how @attr becomes Object class’s class variable?

Your results don’t have anything to do with class_eval–that’s just the
way class variables work in ruby. Class variables
in ruby are not like class variables in other languages. A class
variable in ruby is visible in all super classes and all sub classes:

class A
@@x = ‘a’
end

class B < A
@@x = ‘b’
end

class C < B
@@x = ‘c’
end

puts A.class_variable_get(:@@x)
puts B.class_variable_get(:@@x)
puts C.class_variable_get(:@@x)

–output:–
c
c
c

There is only one @@x variable for the whole inheritance chain.
Because of those results some people think that class variables in
ruby are an abomination and should be removed from the
language–or at the very least never appear in any code.

An alternative is to use class “instance variables”:

class A
@x = ‘a’
end

Instance variables attach themselves to whatever self is at the time
they are created. Then you need to define the accessors in A’s
singleton class.

7stud – wrote in post #1104110:

A class
variable in ruby is visible in all super classes and all sub classes:

Well, that’s not true:

class A
def self.do_stuff
puts @@x
end
end

class B < A
@@x = ‘b’
end

class C < B
def self.do_stuff
puts @@x
end
end

C.do_stuff
A.do_stuff

–output:–
b
1.rb:22:in do_stuff': uninitialized class variable @@x in A (NameError) from 1.rb:37:in

I didn’t recognize that is the very issue you were raising in your post,
i.e. how come a superclass, Object, can see a class variable in a
subclass, A. I don’t have time to figure that out right now, sorry.

Hi,

Note that the effect you are seeing doesn’t occur in this case:

class A
@@x = 10
end

puts A.class_variable_get(:@@x)
puts Object.class_variable_get(:@@x)

–output:–
10
1.rb:6:in class_variable_get': uninitialized class variable @@x in Object (NameError) from 1.rb:6:in

So the problem does have to do with class_eval. I don’t have a
definitive explanation of what’s going on, but “Metaprogramming Ruby”
teaches us that class_eval changes self to the receiver, and the
‘current class’ also changes to the receiver as well. Based on that
‘rule’, it would be natural to assume that class variables appearing for
the first time inside the block would attach to the current class.
However, that is clearly not the case.

Because the problem manifests itself when you use a block (as part of
your class_eval), the block is the key to the problem. Blocks are
closures, which means they can see the variables defined in the
surrounding code. Blocks can also change the values in the surrounding
code. Here is an example of that:

x = 10
code = lambda {puts x; x = ‘hello’}

code.call
puts x

–output:–
10
hello

In your case, the surrounding code is the ‘top level’ where an object
called ‘main’ exists (created by ruby at startup), and main is an object
of class Object. So your block can see those things in the surrounding
scope, or, in other words, your block travels around with a ‘binding’
that includes all the variables in the surrounding code and their
values. I think ruby must look for something in the binding to attach
class variables to–rather than using the ‘current class’.

Your output shows that the class variable attaches to the class Object,
but the details of how that works are not clear to me. However, the
fact that class variables leak out of a block seems like yet another
reason not to use class variables.