Skip to content

Instantly share code, notes, and snippets.

@sumanmukherjee03
Created October 19, 2012 00:17
Show Gist options
  • Save sumanmukherjee03/3915546 to your computer and use it in GitHub Desktop.
Save sumanmukherjee03/3915546 to your computer and use it in GitHub Desktop.
Messing with class variables in ruby
# Execute this file as follows and compare the results in ruby 1.8.*:
#
# ruby class_var_ex.rb
# ruby class_var_ex.rb "include module"
# ruby class_var_ex.rb "include module" "include global"
# ruby "include global"
@@test = 9 if ARGV.last == "include global"
module X
@@test = 3
self.instance_variable_set('@test', 4)
def get_class_variable_from_module
puts "Class variable from module = " + @@test.to_s
end
def get_class_instance_variable_from_module
puts "Class instance variable from module = " + @test.to_s
end
end
class A
extend X
@@test = 1
self.instance_variable_set('@test', 2)
def self.get_class_variable
puts "Class variable = " + @@test.to_s
end
def self.get_class_instance_variable
puts "Class instance variable = " + @test.to_s
end
end
class B < A
B.send(:include, X) if ARGV.first == "include module"
@@test = 2
self.instance_variable_set('@test', 1)
end
def extended_modules_class_variable
puts "Module's class variable = " + X.send(:class_variable_get, '@@test').to_s
end
def extended_modules_class_instance_variable
puts "Module's class instance variable = " + X.instance_variable_get('@test').to_s
end
puts "Values from A >>>>>>>>>>>>>>"
A.get_class_variable_from_module
A.get_class_instance_variable_from_module
A.get_class_variable
A.get_class_instance_variable
puts "Values from B >>>>>>>>>>>>>>"
B.get_class_variable_from_module
B.get_class_instance_variable_from_module
B.get_class_variable
B.get_class_instance_variable
puts "Values from X >>>>>>>>>>>>>>"
extended_modules_class_variable
extended_modules_class_instance_variable
if ARGV.last == "include global"
puts "Values from Global scope >>>>>>>>>>>>>>"
puts "Class variable = " + @@test.to_s
end
@mikong
Copy link

mikong commented Oct 19, 2012

@sumanmukherjee03, what's happening is quite clear (though not necessarily expected). One way to clear things up is to remove all the logs on instance variables. All the logs on instance variables are expected. Whether in the first or second case, it's the same. Including, inheriting, or extending doesn't touch the instance variables. You get:

Values from A >>>>>>>>>>>>>>
Class variable from module = 1
Class variable = 5
Values from B >>>>>>>>>>>>>>
Class variable from module = 1
Class variable = 5
Values from X >>>>>>>>>>>>>>
Module's class variable = 1

Values from A >>>>>>>>>>>>>>
Class variable from module = 5
Class variable = 3
Values from B >>>>>>>>>>>>>>
Class variable from module = 5
Class variable = 3
Values from X >>>>>>>>>>>>>>
Module's class variable = 5

In the first case, B overrides class variable of A, the module's class variable is untouched. This is explained by Erol. And also by this popular article by Nunemaker, http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/.

In the second case, B overrides the class variable of the module because of the include. This is a bit ambiguous to me because I'm expecting 2 things:

  • Declaring a class variable for B, a class that inherits from A, should change the class variable of A
  • Including a module and declaring a class variable, should change the module's class variable.

However, I do expect that only one will be resolved, which turns out to be only the 2nd one. I guess this is one of the reasons why some Rubyists don't like using class variables, and opt to use class instance variables (see Nunemaker's article for this). Class instance variables make sure that values aren't being overridden by class inheritance.

@mikong
Copy link

mikong commented Oct 19, 2012

Here's another thing you can do:

class B < A
  @@test = 8

  B.send(:include, X) if ARGV.first == "include module"

  @@test = 5

  self.instance_variable_set('@test', 6)
end

Those 2 @@test's are not the same. The first one overrides the class variable of A by inheritance. The second one overrides the class variable of the module, by virtue of the include. So it looks like include is changing the scope.

@sumanmukherjee03
Copy link
Author

@mekong, the behavior of the instance variables are totally expected. I punched them in there during the talk just to show the difference and create some context. However, i think the behavior has been resolved. The example i posted does not help answer the questions because i kept the variable names the same. Changing them opened up things.

In case of the class variables, the scope of the class variable in a child is the same as it's parent. So, changing the class variable of a child changes it's parent's class variable too and that behavior is retained through out. So, there is nothing confusing :-) .

I will post 2 more code samples to depict this.

When the module gets included, and the class variable @@test1 is redefined after the inclusion, module X becomes the immediate ancestor of the class B for look up and the class variable being changed in B points to the one initialized in the module X. The scope of that class variable is different than the scope of the class variable of the superclass A, because the superclass of X is not A. Here's the code sample depicting this.

module X
  @@test1 = 3
end

class A
  @@test1 = 9

  def self.get_class_variable
    puts "Class variable = " + @@test1.to_s
  end
end

class B < A
  include X
  @@test1 = 2

  def self.get_class_variable_from_module
    puts "Class variable from module = " + @@test1.to_s
  end
end

puts "Values from A >>>>>>>>>>>>>>"
A.get_class_variable

puts "Values from B >>>>>>>>>>>>>>"
B.get_class_variable_from_module
B.get_class_variable

When the module gets included, but the class variable @@test1 is redefined before the inclusion, the class variable being changed in B points to the one initialized in the class A. Here's the code sample depicting this.

class B < A
  @@test1 = 2
  include X

  def self.get_class_variable_from_module
    puts "Class variable from module = " + @@test1.to_s
  end
end

Continuing from the previous example, when you redefine the class variable again, after the inclusion, the pointer for the class variable changes to the one initialized in module X. So, both class A's class variable and the module X's class variable get changed. Here's the code depicting this.

class B < A
  @@test1 = 2
  include X
  @@test1 = 0

  def self.get_class_variable_from_module
    puts "Class variable from module = " + @@test1.to_s
  end
end

@sumanmukherjee03
Copy link
Author

@mikong and @Erol thanks for the effort and help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment