-1

I have a base controller

class Base
  @@var = 'base'

  def self.result
    @@var
  end
  def self.result=(var)
    @@var = var
  end

  def do_sth
    // do something here
  end
end

and 2 subclasses

class A < Base
  Base.result = 'a'
end

class B < Base
  Base.result = 'b'
end

when visit url_a goes to class A, and then I visit url_b goes to class B, both of them work fine.

But when I switch back to url_a, the Base.result still returns b, why?


edit
I've changed @@var to @var, I got the same result.

4
  • This is a class variable and you're setting it when Ruby reads in the definition of (and defines) the class. If it's not re-reading in the source for the class each time you reference it, it's not going to change. Commented Sep 18, 2014 at 13:54
  • @vee that's different. I've already changed it to instance variable. The result is the same. And I didn't want to new the subclass. Commented Sep 18, 2014 at 14:13
  • @lurker If I don't want to new the subclass, how can I achieve my goal? Commented Sep 18, 2014 at 14:14
  • When you change URL's can you call a class method that selects the class and sets the variable? BTW, changing @@var to @var won't help due to the same issue I pointed out: you are setting the variable during class definition. Commented Sep 18, 2014 at 14:22

1 Answer 1

1

This is an extension of what @lurker has mentioned in the comments to the question. It does not matter whether you use a class variable or a class level instance variable, you will see the same behaviour.

However what happens behind the scenes is different when you use the two types of variables.

Case 1 : Class variable (i.e. @@var)

The value of the class variable is being set during class declaration, i.e. when the Ruby source code is read and that happens only once.

There are two things to keep in mind here :

  • Rails follows lazy loading, i.e. it loads up the definition of a class when it requires it for the first time.
    • So when you hit url_a for the first time class A is loaded, i.e. its source is parsed
    • class B is not yet loaded. It gets loaded later when you hit url_b.
  • When a Ruby source file is parsed, all the code that is outside any function is executed right away. So the Base.result = method calls are executed when the two classes are loaded.

So the sequence of steps would be :

  • In the call to url_a, class A is parsed and Base.result = a sets @@var to a
  • Then in the call to url_b, class b is parsed and Base.result = b sets @@var to b and it remains so for all subsequent calls.

The following code snippet might help understand the second point :

irb(main):033:0> class ParseTest
irb(main):034:1> @@time_now = Time.now
irb(main):035:1> def speak_time
irb(main):036:2> puts @@time_now.to_s
irb(main):037:2> end
irb(main):038:1> end
=> nil
irb(main):039:0> pt = ParseTest.new
=> #<ParseTest:0x007f80758514c8>
irb(main):040:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):041:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):042:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):043:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):044:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):045:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):046:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):047:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):048:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):049:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):050:0> pt.speak_time
2014-09-18 23:15:15 +0530
=> nil
irb(main):051:0> class ParseTest2 < ParseTest
irb(main):052:1> @@time_now = Time.now
irb(main):053:1> end
=> "2014-09-18T23:16:41.911+05:30"
irb(main):054:0> pt.speak_time
2014-09-18 23:16:41 +0530
=> nil
irb(main):055:0> 

As you can see, after the ParseTest class definition was parsed once, the value of @@time_now did not change in any of the subsequent puts. The value of time was what it was when the source code was parsed.

However when I defined the ParseTest2 sub-class and its code was parsed, the same class variable was given a new value of time. This new value is reflected when I print it using the same old object of the base class.

This is what is happening in your code too.

Case 2 : Class level instance variable (i.e. @var in class definition outside any instance function) Now, if instead of a class variable you use an instance variable in the class definition (i.e. outside any function), that is a very different. This case might appear a little confusing, so read and re-read the following code snippet if it appears confusing to you in the first go.

irb(main):089:0> class Base
irb(main):090:1>   @time_now = Time.now
irb(main):091:1> 
irb(main):092:1*   def self.time_now=(time)
irb(main):093:2>     @time_now = time
irb(main):094:2>   end
irb(main):095:1> 
irb(main):096:1*   def self.time_now
irb(main):097:2>     puts @time_now.to_s
irb(main):098:2>   end
irb(main):099:1> end
=> nil
irb(main):100:0> class A < Base
irb(main):101:1>   Base.time_now = Time.now
irb(main):102:1> end
=> "2014-09-18T23:33:26.514+05:30"
irb(main):103:0> Base.time_now
2014-09-18 23:33:26 +0530
=> nil
irb(main):104:0> A.time_now

=> nil
irb(main):105:0> A.time_now = Time.now
=> "2014-09-18T23:34:27.093+05:30"
irb(main):106:0> A.time_now
2014-09-18 23:34:27 +0530
=> nil
irb(main):107:0> Base.time_now
2014-09-18 23:33:26 +0530
=> nil
irb(main):108:0> 

The class level instance variable is private to that class. It does not get passed on / shared during inheritance. So every class in the inheritance hierarchy has its own set of instance variables. However the methods do get passed on and those methods act on the instance variable of the class on which they were called. So depending on which class you call the time_now= setter method, the corresponding instance variable is set.

In your case you are always referring to the instance variable of the Base class. So the same set of steps happen as described in the previous case

  • In the call to url_a, class A is parsed and Base.result = a sets @var of Base to a
  • Then in the call to url_b, class b is parsed and Base.result = b sets @var of Base to b and it remains so for all subsequent calls.
Sign up to request clarification or add additional context in comments.

4 Comments

thanks for you long long explanation. I think I understand what happened behind @@ & @ now.
But my work still cannot go on. I want a method defined in a base class, return a variable changed in different subclasses. Is there another way to do it?
Use proper instance methods and instance variables and set the values inside those methods. However it would be a lot more clearer if you tell us what you are trying to achieve. There might be better ways of achieving the same.
I may have tens of controllers they have similar methods in my rails app. I want to move those similar methods into a BaseController, then I can keep less code in those controllers. Now I am blocked by an issue as this question mentioned, each page related to a controller will get a page title from the controller, the titles are different, but all return by a method in the BaseController. You can take a look here, there are three controllers, I think you'll know what I want.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.