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:
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:
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
- module
A
is included
- the
included
callback does klass.send(:cattr_accessor, :my_name)
.
- an
@@my_name
is created in class B
that is set to 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)
Maybe the following will illustrate. Imagine we'd just got as far as your b = B.new
and we do the following:
>> 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.)