Alternatives to class variables

Discussion in 'Ruby' started by Sam Hendley, Aug 20, 2008.

  1. Sam Hendley

    Sam Hendley Guest

    Hello, this is my first post to this list and I have only been using
    ruby for a couple of weeks so forgive me if its overly simple, I spent a
    few hours searching and wasn't able to figure this out myself.

    What I am trying to do is use some meta-programming techniques to create
    a simple DSL that allows me to define functions dynamically and collect
    other information that I can iterate over. I have an implementation that
    is using "Class Variables" that is working but I have to specify all of
    the functions that use the @@ variables in all of the subclasses which
    violates DRY and my sensiblities. What I am looking for is a way to have
    subclass specific information used by a common class.

    Below is the implementation I have (boiled down to its simplest possible
    form with a testcase to show the behavior I need). I know there must be
    a better way to do this but my ruby-fu is not up to the task yet, im
    really interested to see what the idiomatic ruby way to do this is.

    Sam Hendley

    ------------ Example.rb --------------
    require 'test/unit'

    module MethodCollector
    def self.included( klass)
    klass.extend ClassMethods
    end
    module ClassMethods
    def addpoint(array_name, name)
    is_defined = send:)class_variable_defined?, array_name)
    if !is_defined
    arr = Array.new
    send:)class_variable_set, array_name, arr)
    elsif
    arr = send:)class_variable_get,array_name)
    end
    arr << name
    end

    def add_method(name)
    method_name = (name.to_s).to_sym
    self.send :define_method, method_name do
    "MethodName is: #{method_name}"
    end
    addpoint:)@@extra_methods, name)
    end

    end
    end

    class MethodAdderBase
    include MethodCollector

    # ideally I would define this method (and any others with other
    common functionality that
    # use the "class specific" variables

    #def extra_methods
    # @@extra_methods
    #end
    end

    class Foo < MethodAdderBase

    add_method :foo_1
    add_method :foo_2
    add_method :foo_3

    def extra_methods
    @@extra_methods
    end
    end

    class Bar < MethodAdderBase

    add_method :bar_1
    add_method :bar_2
    add_method :bar_3
    add_method :bar_4

    def extra_methods
    @@extra_methods
    end
    end

    class Check < Test::Unit::TestCase
    def test_methods_created
    foo = Foo.new
    bar = Bar.new

    assert_equal(foo.foo_1, "MethodName is: foo_1")
    assert_equal(bar.bar_1, "MethodName is: bar_1")
    end
    def test_methods_collected
    foo = Foo.new
    bar = Bar.new

    assert_equal(foo.extra_methods.size, 3)
    assert_equal(bar.extra_methods.size, 4)
    end
    end
     
    Sam Hendley, Aug 20, 2008
    #1
    1. Advertisements

  2. Sam Hendley

    Pit Capitain Guest

    Welcome Sam. Before answering, let me ask you two more questions :)

    1) Why are you asking the instances about the extra methods? Instead of

    Foo.new.extra_methods

    I would expect

    Foo.extra_methods

    Or do you want to define extra methods for individual objects, too?

    2) How deep will your class hierarchy be? What about subclasses of
    Foo, for example, after

    class SubFoo < Foo
    add_method :foo_4
    end

    what should be the result of

    SubFoo.new.extra_methods.size

    BTW: the normal indentation of Ruby code is with two spaces per level.

    Regards,
    Pit
     
    Pit Capitain, Aug 21, 2008
    #2
    1. Advertisements

  3. Sam Hendley

    Sam Hendley Guest

    Thanks for the response.

    1) I am asking the individual instances for their "extra_methods"
    because I am creating collections of these objects that higher level
    objects that use the meta-data I collect during the method declaration
    to do some cool things.

    I hadn't thought of doing something like
    instance.class.new.extra_methods but I'm not sure if it would work
    (without being just as ugly as it is now) because of the class variable
    inheritance rules.

    2) the class hierarchy won't be any deeper than one level (Base +
    SubClass).

    I'm open to anything at this point, it occured to me that I could just
    store all of the extra information directly in another object (better
    the base class?) using a hash on the subclass type. The objects
    themselves don't need to use meta-data I'm collecting.

    < snip 5 minutes >

    Well your questions got me started down the right road and I have a
    solution that solves my immediate problem (though I am still interested
    in the "right way" that keeps the infomration with the classes
    themselves).

    I used a class level hash on the base class that looks up the extra
    methods based on self.class on a call. I applied this to my "real world"
    problem as well and it worked perfectly, should have thought of this
    earlier, but I got fixated on keeping all the information in the classes
    themselves.

    For completeness sake and if someone finds this on google here is my
    solution:

    module MethodCollector
    def self.included( klass)
    klass.extend ClassMethods
    end
    module ClassMethods
    def addpoint(name)
    MethodAdderBase.extra_methods[self] ||= []
    MethodAdderBase.extra_methods[self] << name
    end

    def add_method(name)
    method_name = (name.to_s).to_sym
    self.send :define_method, method_name do
    "MethodName is: #{method_name}"
    end
    addpoint(name)
    end

    end
    end

    class MethodAdderBase
    include MethodCollector
    @@extra_methods = {}

    def self.extra_methods
    @@extra_methods
    end

    def extra_methods
    @@extra_methods[self.class]
    end
    end
     
    Sam Hendley, Aug 21, 2008
    #3
  4. Sam Hendley

    Pit Capitain Guest

    If you don't need to support deep class hierarchies you don't need
    class variables, so I'd use the simpler class instance variables
    instead. I'm not sure why you decided to split the functionality into
    a module and a base class, but if you want this structure, here's how
    I would code it (but I don't claim to know the "right way"...)

    module MethodCollector
    def self.included( klass)
    klass.extend ClassMethods
    end
    module ClassMethods
    def addpoint(name)
    extra_methods << name
    end

    def add_method(name)
    define_method name.to_s do
    "MethodName is: #{name}"
    end
    addpoint(name)
    end
    end
    end

    class MethodAdderBase
    include MethodCollector

    def self.extra_methods
    @extra_methods ||= []
    end

    def extra_methods
    self.class.extra_methods
    end
    end

    Regards,
    Pit
     
    Pit Capitain, Aug 22, 2008
    #4
    1. Advertisements

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.