Hello,
I am trying to learn ruby and am experimenting with a Mixin.
I have the following test module: http://pastie.org/816001
On line 17, the class variable @@name gives an error:
uninitialized class variable @@name in TestMixin::ClassMethods
I understand it is because that’s in a module within the TestMixin
module and that the @@name class variable is created in the including
class which is in a separate scope.
Can someone advise how this should be written so that a class method in
a mixin can access a class variable created by the mixin ?
Instance methods work fine.
Thanks in advance.
John L. wrote:
Can someone advise how this should be written so that a class method in
a mixin can access a class variable created by the mixin ?
Class variables are a pain, for exactly this sort of reason. Instance
variables of a class are much easier to handle and understand.
module TestMixin
def self.included(base)
p “TextMixin included in #{base}”
base.class_eval {
@name = “foobar”
}
base.extend ClassMethods
end
module ClassMethods
def name
@name
end
def name=(x)
@name=(x)
end
def class_method(str)
$stderr.puts "this is a class method in TestMixin #{str}"
$stderr.puts "the class variable is #{name}"
end
end
def name
self.class.name
end
def name=(x)
self.class.name=(x)
end
def instance_method
$stderr.puts “this is an instance method in TextMixin”
$stderr.puts “the class variable is #{name}”
end
end
class Foo
include TestMixin
class_method “foo”
def initialize
instance_method
end
end
Foo.new
Brian C. wrote:
Class variables are a pain, for exactly this sort of reason.
Yes, I’m getting that feeling! My example test was just a simplified
model to understand how it worked and, in the case of the example, I can
see instance variables provide a solution.
What I’m trying to do is end up with a module mixed into a number of
classes that will each call a mixed-in class method to add values to a
class variable that will be a hash. Other instance methods in the mixin
will then make use of the information in the class variable hash.
So I think I really need a class variable.
John L. wrote:
Brian C. wrote:
Class variables are a pain, for exactly this sort of reason.
Yes, I’m getting that feeling! My example test was just a simplified
model to understand how it worked and, in the case of the example, I can
see instance variables provide a solution.
What I’m trying to do is end up with a module mixed into a number of
classes that will each call a mixed-in class method to add values to a
class variable that will be a hash. Other instance methods in the mixin
will then make use of the information in the class variable hash.
So I think I really need a class variable.
But you’re probably wrong. What you probably want is a class instance
variable – a weird concept, to be sure, until you recall that in Ruby
classes are instances of class Class. So:
class MyClass
@@class_var = ‘foo’
@class_ivar = ‘bar’
def self.class_method
puts @@class_var
puts @class_ivar
end
end
puts MyClass.class_method # prints ‘foo’ and ‘bar’
In other words, class @instance variables act just like @@class
variables but without the problems.
Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]
Marnen Laibow-Koser wrote:
So I think I really need a class variable.
But you’re probably wrong. What you probably want is a class instance
variable – a weird concept, to be sure, until you recall that in Ruby
classes are instances of class Class.
snip
In other words, class @instance variables act just like @@class
variables but without the problems.
Ok so I get that point. I’ve now got working code, well almost. I mix
the module into multiple classes and each gets their own instance of the
variable, which is not what I need.
Here’s the code: http://pastie.org/816654
There are three classes, One, Two and Three. Classes One and Two both
mix in the module. Class Three is a subclass of class Two and therefore
does not directly mix in the module.
The output i get demonstrates that each class gets its own instance of
the variable that I want to be shared across all three. Here is the
output:
TestMixin included in One
b added for class One : a b
c added for class One : a b c
TestMixin included in Two
d added for class Two : a d
e added for class Two : a d e
f added for class Three : f
g added for class Three : f g
the list for class One is a b c
the list for class Two is a d e
the list for class Three is f g
What I want to get is this:
TestMixin included in One
b added for class One : a b
c added for class One : a b c
TestMixin included in Two
d added for class Two : a b c d
e added for class Two : a b c d e
f added for class Three : a b c d e f
g added for class Three : a b c d e f g
the list for class One is a b c d e f g
the list for class Two is a b e d e f g
the list for class Three is a b c d e f g
John L. wrote:
Ok so I get that point. I’ve now got working code, well almost. I mix
the module into multiple classes and each gets their own instance of the
variable, which is not what I need.
If everything which mixes in this module shares the same state, then you
can use a module instance variable.
If you want each subtree of related classes to share state, then one
option is to use a class instance variable and use the class hierarchy
to find where the value is held. Along the lines of:
def name
defined?(@name) ? @name : super
end
Another option is for each class to have its own @name instance
variable, but for them all to refer to the same object (a Hash in your
case); in self.included you initialize @name to the same as @name in the
parent class, if it exists.
But in your case class One and Two are unrelated but you want them to
share state anyway, so I’d go with a module instance variable. This
passes your test:
module TestMixin
@list = “a”
def self.list
@list
end
def self.included(base)
puts “TestMixin included in #{base}”
base.extend ClassMethods
end
module ClassMethods
def list
TestMixin.list
end
def class_method(str)
self.list << " #{str}"
puts "#{str} added for class #{self} : #{list}"
end
end
def list
TestMixin.list
end
def instance_method
puts “the list for class #{self.class} is #{list}”
end
end
class One
include TestMixin
class_method ‘b’
class_method ‘c’
end
class Two
include TestMixin
class_method ‘d’
class_method ‘e’
end
class Three < Two
class_method ‘f’
class_method ‘g’
end
one = One.new
two = Two.new
three = Three.new
one.instance_method
two.instance_method
three.instance_method
Thank you Brian, that sure does what I need. I will now go away and try
it in a real application. Thank you all - there’s some useful techniques
highlighted in these answers.