Running this code:
运行这段代码:
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
Breaks like this:
优惠如下:
My other name is B
TypeError: (eval):34:in `+': can't convert nil into String
If we swap last two blocks of code (so that b.set_my_name_attr
gets called before b.set_my_name_var
), everything is works fine.
如果我们交换最后两个代码块(b)set_my_name_attr在b.set_my_name_var)之前被调用,一切正常。
It looks like it treats @@my_name
as class variable of module A
, not class B
(as I would expect it to). Isn't it confusing? Where can read more about module class variables?
看起来它将@my_name作为模块A的类变量,而不是类B(正如我所期望的那样)。不是它困惑?在哪里可以阅读更多关于模块类变量的内容?
1 个解决方案
#1
3
When you have your set_my_name_var
method in module A
doing @@my_name = 'A'
this is setting a module variable in A
. This behaviour doesn't change when the method is called via an including class. This also leads to another fact that sometimes catches people out - if you were to include A
in multiple classes there is only one instance of @@my_name
, not one instance per including class. The following example illustrates this:
当在模块A中有set_my_name_var方法时,这是在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"
Update
更新
I think I have figured out what is happening that will explain why it doesn't seem to work the way you expect. cattr_reader
(and by extension cattr_accessor
) contains the following:
我想我已经弄明白了正在发生的事情,这就可以解释为什么事情并不是你所期望的那样。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...
The following sequence takes place:
以下顺序发生:
-
B
is defined - B是定义
- module
A
is included - 模块包含一个
- the
included
callback doesklass.send(:cattr_accessor, :my_name)
. - 所包含的回调是klass。发送(:cattr_accessor:my_name)。
- an
@@my_name
is created in classB
that is set tonil
. - 在类B中创建一个@ @@my_name,并将其设置为nil。
Without the cattr_accessor
then after calling set_my_name_var
when you say @@my_name
within B
it would refer to the module's variable. But with the cattr_accessor
in place a variable with the same name now exists in the class so if we say @@my_name
within B
we get the value of B
's variable in preference to A
's. This is what I meant by masking. (B
's variable has got in the way of us seeing A
's)
如果没有cattr_accessor,那么在调用set_my_name_var之后,当你在B中输入@@my_name时,它将引用模块的变量。但是有了cattr_accessor,类中就存在一个同名的变量,所以如果我们在B中输入@@my_name,我们会得到B的变量的值优先于a。这就是我说的掩蔽。(B的变量阻碍了我们看到A)
Maybe the following will illustrate. Imagine we'd just got as far as your b = B.new
and we do the following:
也许下面的例子可以说明这一点。假设我们得到了b = b。新,我们做以下工作:
>> 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
I think cattr_reader
does this to avoid uninitialized class variable
errors if you try to use the getter before the setter. (class variables don't default to nil
in the same way that instance variables do.)
我认为cattr_reader这样做是为了避免在setter之前使用getter方法来避免未初始化的类变量错误。(类变量不像实例变量那样默认为nil。)
#1
3
When you have your set_my_name_var
method in module A
doing @@my_name = 'A'
this is setting a module variable in A
. This behaviour doesn't change when the method is called via an including class. This also leads to another fact that sometimes catches people out - if you were to include A
in multiple classes there is only one instance of @@my_name
, not one instance per including class. The following example illustrates this:
当在模块A中有set_my_name_var方法时,这是在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"
Update
更新
I think I have figured out what is happening that will explain why it doesn't seem to work the way you expect. cattr_reader
(and by extension cattr_accessor
) contains the following:
我想我已经弄明白了正在发生的事情,这就可以解释为什么事情并不是你所期望的那样。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...
The following sequence takes place:
以下顺序发生:
-
B
is defined - B是定义
- module
A
is included - 模块包含一个
- the
included
callback doesklass.send(:cattr_accessor, :my_name)
. - 所包含的回调是klass。发送(:cattr_accessor:my_name)。
- an
@@my_name
is created in classB
that is set tonil
. - 在类B中创建一个@ @@my_name,并将其设置为nil。
Without the cattr_accessor
then after calling set_my_name_var
when you say @@my_name
within B
it would refer to the module's variable. But with the cattr_accessor
in place a variable with the same name now exists in the class so if we say @@my_name
within B
we get the value of B
's variable in preference to A
's. This is what I meant by masking. (B
's variable has got in the way of us seeing A
's)
如果没有cattr_accessor,那么在调用set_my_name_var之后,当你在B中输入@@my_name时,它将引用模块的变量。但是有了cattr_accessor,类中就存在一个同名的变量,所以如果我们在B中输入@@my_name,我们会得到B的变量的值优先于a。这就是我说的掩蔽。(B的变量阻碍了我们看到A)
Maybe the following will illustrate. Imagine we'd just got as far as your b = B.new
and we do the following:
也许下面的例子可以说明这一点。假设我们得到了b = b。新,我们做以下工作:
>> 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
I think cattr_reader
does this to avoid uninitialized class variable
errors if you try to use the getter before the setter. (class variables don't default to nil
in the same way that instance variables do.)
我认为cattr_reader这样做是为了避免在setter之前使用getter方法来避免未初始化的类变量错误。(类变量不像实例变量那样默认为nil。)