为什么不能在rails的控制器中重新分配类变量

时间:2014-09-18 13:49:40

标签: ruby-on-rails ruby-on-rails-4.1 ruby-2.1

我有一个基本控制器

class Base
  @@var = 'base'

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

  def do_sth
    // do something here
  end
end

和2个子类

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

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

当访问url_a转到class A,然后我访问url_b转到class B,两者都运行正常。

但当我切换回url_a时,Base.result仍会返回b,为什么?


修改
我已将@@var更改为@var,我得到的结果相同。

1 个答案:

答案 0 :(得分:1)

这是@lurker在问题评论中提到的内容的扩展。无论您使用类变量还是类级实例变量都没关系,您将看到相同的行为。

然而,当您使用两种类型的变量时,幕后发生的情况会有所不同。

案例1:类变量(即@@var

在类声明期间设置类变量的值,即读取Ruby源代码并且只发生一次。

这里要记住两件事:

  • Rails遵循延迟加载,即它在第一次需要时加载类的定义。
    • 因此,当您第一次点击url_a class A时,即已解析其来源
    • class B尚未加载。当您点击url_b时,它会被加载。
  • 解析Ruby源文件时,立即执行任何函数之外的所有代码。因此,在加载两个类时执行Base.result =方法调用。

所以步骤的顺序是:

  • 在致电url_a时,class A已解析,Base.result = a@@var设为a
  • 然后,在对url_b的调用中,class b被解析,Base.result = b@@var设置为b,对于所有后续调用,它仍然如此。

以下代码段可能有助于理解第二点:

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> 

正如您所看到的,在解析ParseTest类定义一次后,@@time_now的值在随后的任何puts中都没有变化。时间的价值就是解析源代码的时间。

但是,当我定义ParseTest2子类并解析其代码时,同一个类变量被赋予了新的time值。当我使用基类的相同旧对象打印它时,会反映这个新值。

这也是您的代码中发生的事情。

案例2:类级实例变量(即任何实例函数外的类定义中的@var) 现在,如果不是类变量而是在类定义中使用实例变量(即在任何函数之外),那就非常不同了。这种情况可能看起来有点令人困惑,因此如果第一次看起来让您感到困惑,请阅读并重新阅读以下代码段。

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> 

类级别实例变量对class是私有的。它在继承期间不会被传递/共享。因此,继承层次结构中的每个类都有自己的一组实例变量。但是,方法确实被传递,并且这些方法作用于调用它们的类的实例变量。因此,根据您调用time_now= setter方法的类,将设置相应的实例变量。

在您的情况下,您始终引用Base类的实例变量。因此,相同的一组步骤发生在前一个案例中所描述的

  • 在致电url_a时,class A已解析,Base.result = a@var设为Basea
  • 然后,在对url_b的调用中,class b被解析,Base.result = b@var Base设置为b,它仍然是所有后续电话。