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.
on 2010-02-09 10:49
on 2010-02-09 14:46
John Lane 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
on 2010-02-09 16:45
Brian Candler 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.
on 2010-02-09 17:17
John Lane wrote: > Brian Candler 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 marnen@marnen.org
on 2010-02-09 18:43
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
on 2010-02-09 22:33
John Lane 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
on 2010-02-09 22:48
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.
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.