Rails:cattr_accessor和类变量

时间:2010-07-16 10:18:53

标签: ruby-on-rails ruby activesupport

运行此代码:

module A
  def self.included(klass)
    klass.send(:cattr_accessor, :my_name)
  end

  def set_my_name_var
    @@my_name = 'A' # does NOT work as expected
  end

  def set_my_name_attr
    self.class.my_name = 'A' # works as expected
  end
end

class B
  include A

  cattr_accessor :my_other_name

  def set_my_other_name_var
    @@my_other_name = 'B' # works
  end

  def set_my_other_name_attr
    self.class.my_other_name = 'B' # works
  end
end

b = B.new

b.set_my_other_name_var
puts "My other name is " + B.my_other_name
b.set_my_name_var
puts "My name is " + B.my_name

b.set_my_other_name_attr
puts "My other name is " + B.my_other_name
b.set_my_name_attr
puts "My name is " + B.my_name

打破这样:

My other name is B
TypeError: (eval):34:in `+': can't convert nil into String

如果我们交换最后两个代码块(以便在b.set_my_name_attr之前调用b.set_my_name_var),一切正常。

看起来它将@@my_name视为模块A的类变量,而不是类B(正如我所期望的那样)。这不是很困惑吗?哪里可以阅读更多关于模块类变量的内容?

1 个答案:

答案 0 :(得分:3)

如果set_my_name_var模块中A方法执行@@my_name = 'A',则会在A中设置模块变量。通过包含类调用方法时,此行为不会更改。这也会导致另一个事实,即有时会将人们赶出去 - 如果您要在多个班级中加入A,则只有一个@@my_name的实例,每个包含一个类。以下示例说明了这一点:

module Example
  def name=(name)
    @@name = name
  end

  def name
    @@name
  end
end

class First
  include Example
end

class Second
  include Example
end

irb(main):066:0> f = First.new
=> #<First:0x2d4b80c>
irb(main):067:0> s = Second.new
=> #<Second:0x2d491d8>
irb(main):068:0> f.name = 'Set via f'
=> "Set via f"
irb(main):069:0> s.name
=> "Set via f"

<强>更新

我想我已经弄清楚发生了什么,这将解释为什么它似乎没有按照你期望的方式工作。 cattr_reader(以及cattr_accessor扩展名)包含以下内容:

class_eval(<<-EOS, __FILE__, __LINE__)
  unless defined? @@#{sym}  # unless defined? @@hair_colors
    @@#{sym} = nil          #   @@hair_colors = nil
  end

  # code to define reader method follows...

发生以下顺序:

  • B已定义
  • 包含模块A
  • included回调执行klass.send(:cattr_accessor, :my_name)
  • @@my_name班级中创建B,设置为nil

如果在cattr_accessorset_my_name_var之后调用@@my_name,则在B之后没有cattr_accessor,它将引用模块的变量。但是当@@my_name到位时,类中现在存在一个具有相同名称的变量,因此如果我们在B中说B,我们会得到A变量的值偏好B的。这就是我掩盖的意思。 (A的变量阻碍了我们看到b = B.new

以下可能会说明。想象一下,我们刚刚到达>> A.class_variables => [] # No methods called on A yet so no module variables initialised >> B.class_variables => ["@@my_other_name", "@@my_name"] # these exist and both set to nil by cattr_accessor >> B.send(:class_variable_get, '@@my_name') => nil # B's @@my_name is set to nil >> b.set_my_name_var # we call set_my_name_var as you did in the question => "A" >> A.send(:class_variable_get, '@@my_name') => "A" # the variable in the module is to to 'A' as you expect >> B.send(:class_variable_get, '@@my_name') => nil # but the variable in the class is set to nil >> B.my_name => nil # B.my_name accessor has returned the variable from the class i.e. nil ,我们会做以下事情:

cattr_reader

如果您尝试在setter之前使用getter,我认为uninitialized class variable可以避免nil错误。 (类变量不像实例变量那样默认为{{1}}。)

相关问题