Mixin module with class variables and class methods

Discussion in 'Ruby' started by John Lane, Feb 9, 2010.

  1. John Lane

    John Lane Guest

    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.
    --
    Posted via http://www.ruby-forum.com/.
     
    John Lane, Feb 9, 2010
    #1
    1. Advertising

  2. 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
    --
    Posted via http://www.ruby-forum.com/.
     
    Brian Candler, Feb 9, 2010
    #2
    1. Advertising

  3. John Lane

    John Lane Guest

    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.

    --
    Posted via http://www.ruby-forum.com/.
     
    John Lane, Feb 9, 2010
    #3
  4. 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

    --
    Posted via http://www.ruby-forum.com/.
     
    Marnen Laibow-Koser, Feb 9, 2010
    #4
  5. John Lane

    John Lane Guest

    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






    --
    Posted via http://www.ruby-forum.com/.
     
    John Lane, Feb 9, 2010
    #5
  6. 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
    --
    Posted via http://www.ruby-forum.com/.
     
    Brian Candler, Feb 9, 2010
    #6
  7. John Lane

    John Lane Guest

    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.
    --
    Posted via http://www.ruby-forum.com/.
     
    John Lane, Feb 9, 2010
    #7
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Florian Frank

    Extract methods in a class to mixin?

    Florian Frank, Sep 10, 2003, in forum: Ruby
    Replies:
    0
    Views:
    118
    Florian Frank
    Sep 10, 2003
  2. Michael Roth

    Mixin of class methods?

    Michael Roth, Sep 24, 2005, in forum: Ruby
    Replies:
    8
    Views:
    150
    michele
    Sep 26, 2005
  3. James Coglan
    Replies:
    4
    Views:
    114
    ara.t.howard
    Nov 15, 2008
  4. Sijo Kg

    class, Module and Mixin

    Sijo Kg, Sep 24, 2009, in forum: Ruby
    Replies:
    1
    Views:
    136
    Robert Klemme
    Sep 24, 2009
  5. JP Billaud

    Module Mixin & Class Variables

    JP Billaud, Mar 6, 2011, in forum: Ruby
    Replies:
    0
    Views:
    148
    JP Billaud
    Mar 6, 2011
Loading...

Share This Page