运行此代码:
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
(正如我所期望的那样)。这不是很困惑吗?哪里可以阅读更多关于模块类变量的内容?
答案 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_accessor
内set_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}}。)