0

I wrote some code to learn about the class_eval method. My understanding is that Foo.class_eval will set self to "Foo" and evaluate the block below in that context, just as saying "class Foo" will do. However, the code below shows that setting a class variable using class_eval will assign the class variable at the toplevel: to "Object". Why does this happen?

class Foo
end

class Foo
  puts self
  # => Foo
  @@class_var = "hello"
end

puts Object.class_variables
# => []

Foo.class_eval do
    puts self
    # => Foo
    @@class_var = "hi"
    # => warning: class variable access from toplevel
end

puts Object.class_variables
# => @@class_var
nickackerman42
  • 415
  • 4
  • 14
  • 1
    If you use single `@` it should work correctly. See the first annswer at https://stackoverflow.com/questions/1251352/ruby-inherit-code-that-works- with-class-variables – max pleaner Feb 14 '18 at 17:14
  • Using single @ does get rid of the warning, but I think that still doesn't answer my question: if self is Foo in the class_eval block, why does the class variable I set end up a class variable of Object? – nickackerman42 Feb 14 '18 at 18:10
  • all classes inherit from object, and double @ variables are shared with the whole inheritance chain. Like the answer I linked says, they're more like globals. Don't blame you for being confused. – max pleaner Feb 14 '18 at 18:45
  • Mhm. Double @ variables are shared down the inheritance chain. Object is above Foo in the chain, so I don't see why @@class_var belongs to Object. – nickackerman42 Feb 14 '18 at 18:54
  • i guess that's a good point, I don't know. – max pleaner Feb 14 '18 at 19:42

1 Answers1

0

As pointed out in comments a single @ would do what you want, now if you were running the code from inside another class @@ would work but it would set a class variable for both Foo, and whatever class you running from:

class Foo
  puts self
  # => Foo
  @@class_var = "hello"
end

class Bar
  def self.main
    puts Object.class_variables
    # => []

    Foo.class_eval do
      puts self
      # => Foo
      @@class_var = "hi"
    end
  end
end

Bar.main
p [Bar.class_variables]
#[[:@@class_var]]
p [Foo.class_variables]
#[[:@@class_var]]

That has to do with proc or block scope, I was expecting it to set only @@class_var only on Bar class but it actually sets on both, weird.

Tiago Lopo
  • 7,619
  • 1
  • 30
  • 51
  • This is illuminating, but I'm not sure it fully answers my question. It shows that when class_eval is called within a "class Bar" block, Bar gets the class variable. In my example, there's similar (but still confusing) behavior: class_eval is called within the context of "Object" (top-level class) and Object is assigned the class variable. – nickackerman42 Feb 14 '18 at 19:03
  • yeah there must be something special about context, if you think there that's the entry-point of your app, so I assume that's some magic going on. if you rename Bar to Object it would still work just like monkey patch does – Tiago Lopo Feb 14 '18 at 19:08
  • also if you run `p self` form the top level context you don't see `Object` but you see `main` – Tiago Lopo Feb 14 '18 at 19:13