1

When I use Class.new, for some reason the class variables of the resulted classes interfere with each other:

# ruby 2.1.6p336 (2015-04-13 revision 50298) [i686-linux]

result = Class.new do
  p self # #<Class:0xb7cd5624>

  @@foo = 1
  def foo
    p @@foo
  end
end

result2 = Class.new do
  p self  # #<Class:0xb7cd54d0>

  @@foo = 2
  def foo
    p @@foo
  end
end

result.class_variable_set(:@@foo, 3)
result.new.foo   # expected 3, output 3
result2.new.foo  # expected 2, output 3

Why? What is happening under the hood?

Also there are related Warning messages but I am unable to understand what they mean, neither to find a good description.

warning: class variable access from toplevel

The closest clues I've found so far are:

The access to the class variable is considered top level because the class keyword does not define a class name that would provide a scope to hold the class variable.

(c) http://japhr.blogspot.ru/2009/06/more-information-about-class-variables.html

Since you're not creating a class with the class keyword, your class variable is being set on Object, not Test

(c) https://stackoverflow.com/a/10712458/1432640

Could somebody please describe in details why it happens and why it is so different from when I use class keyword?

Community
  • 1
  • 1
stillwaiting
  • 415
  • 1
  • 5
  • 14

3 Answers3

1

Let's walk through it:

result = Class.new do
  p self # #<Class:0xb7cd5624>

  @@foo = 1
  def foo
    p @@foo
  end
end

At this point, result has been created as an instance of Class and the class variable for Class, @@foo, is set to 1.

result2 = Class.new do
  p self  # #<Class:0xb7cd54d0>

  @@foo = 2
  def foo
    p @@foo
  end
end

At this point, result2 has been created as an instance of Class and the class variable for Class, @@foo, is set to 2.

result.class_variable_set(:@@foo, 3)

This sets the class variable in class Class with name @@foo to the value 3.

result.new.foo   # expected 3, output 3

Output is 3 because @@foo is set to 3 from the class_variable_set above.

result2.new.foo  # expected 2, output 3

result2 has already been created before this statement occurs, so @@foo = 2 is not executed. The construction above for result2 = Class.new ... created the instance already. result2.new creates a new instance of Class, and it exercises the constructor for Class, not the code you have above for result2 = Class.new ... (that had already been executed hen you constructed result2). The base Class constructor does not know about @@foo and doesn't use it or set it. So the value of @@foo is still 3.


Regarding the warning message:

warning: class variable access from toplevel

you might want to Google search the warning message, as there are several good links to read. In particular, you might want to look at The many gotchas of Ruby class variables. This brief article also explains what I'm describing above.


If you want to have separate class variables for your new, dynamically created classes, you can create/set the class variables after the class is created. You can name the classes by assigning them to a constant:
1.9.2-p330 :017 > result.class_variable_set(:@@bar, 3)
 => 3
1.9.2-p330 :018 > result2.class_variable_set(:@@bar, 4)
 => 4
1.9.2-p330 :019 > R1 = result
 => R1
1.9.2-p330 :020 > R2 = result2
 => R2
1.9.2-p330 :021 > class R1
1.9.2-p330 :022?>   def bar
1.9.2-p330 :023?>     p @@bar
1.9.2-p330 :024?>     end
1.9.2-p330 :025?>   end
 => nil
1.9.2-p330 :026 > class R2
1.9.2-p330 :027?>   def bar
1.9.2-p330 :028?>     p @@bar
1.9.2-p330 :029?>     end
1.9.2-p330 :030?>   end
 => nil
1.9.2-p330 :031 > R1.new.bar
3
 => 3
1.9.2-p330 :032 > R2.new.bar
4
 => 4
1.9.2-p330 :033 >
lurker
  • 56,987
  • 9
  • 69
  • 103
  • _At this point, result has been created as an instance of Class and the class variable for Class, @@foo, is set to 1._ I think this is where I'm stumbled. In the docs it is written: _Creates a new anonymous (unnamed) class ... (http://ruby-doc.org/core-1.9.3/Class.html#method-c-new)_ Therefore, its class variables should belong to this new anonymous (unnamed) class, shouldn't they? – stillwaiting Jun 15 '15 at 10:21
  • @stillwaiting That's an excellent point. While the anonymous class is still unnamed and anonymous, the variable is common (part of the base `Class` object). If you named the new, anonymous class and created class variables in the classes by name, they would be separate. I'll update my answer to show that. – lurker Jun 15 '15 at 11:25
  • Thanks, but not sure I follow. You write _"If you want to have separate class variables for your new, dynamically created classes, you can create/set the class variables after the class is created."_, but this is what I'm doing: first I create an instance of a Class, and after that I'm trying to assign a class variable. If I remove "@@foo=1" from the block, nothing changes actually, the output is still the same. Neither if I assign the result or result2 to R1 or R2. I understand that everything runs smoothly with class block, but this is not the case I think. Am I missing somethinig? – stillwaiting Jun 15 '15 at 12:28
  • @stillwaiting I'm distinguishing between *assigning* and *creating*. You *assigned* the class variable `@@foo` after creating the classes. I *created* the `@@bar` class variable *after* the new classes were created, not part of the `Class.new`. – lurker Jun 15 '15 at 12:30
0

Class variables are shared throughout the class hierarchy. You might want to look into using class instance variables for your use case.

[42] pry(main)> class Parent
[42] pry(main)*   @@foo = "Parent"
[42] pry(main)*  end

[43] pry(main)>  class Thing1 < Parent
[43] pry(main)*    @@foo = "Thing1"
[43] pry(main)*  end

[44] pry(main)>  class Thing2 < Parent
[44] pry(main)*    @@foo = "Thing2"
[44] pry(main)*  end

[45] pry(main)> Thing1.class_eval("@@foo")
=> "Thing2"
[46] pry(main)> Parent.class_eval("@@foo")
=> "Thing2"
[47] pry(main)> Class.class_eval("@@foo")
=> "Thing2"

Ruby, class variables and inheritance ? super class @@var gets changed?

Community
  • 1
  • 1
fylooi
  • 3,840
  • 14
  • 24
0

Blocks create a new nested lexical scope and close over their surrounding lexical scope. They do, however, not change dynamic scope.

Dynamic scope stays the same within the block as outside of it, i.e. the value of self, the value of the default definee, the current class.

Class variables at the top-level become class variables of Object. Moreover, the two blocks share the same outer scope. Therefore, they share the same class variables.

Note that some methods actually do change the dynamic scope of the block, e.g. instance_exec and instance_eval change the value of self.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Thanks. Wondering what "dynamic scope" is then? If I print "self", all they are different. Outside it prints "main", inside the first block it prints #, in the 2nd one it is #.... – stillwaiting Jun 15 '15 at 12:33