Alternatives to class variables

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

···

--
Posted via http://www.ruby-forum.com/.

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

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

···

2008/8/20 Sam Hendley <sam.hendley@plymouthsystems.com>:

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.

Pit Capitain wrote:

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.

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

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

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

···

2008/8/20 Sam Hendley <sam.hendley@plymouthsystems.com>:

--
Posted via http://www.ruby-forum.com/\.

Well your questions got me started down the right road and I have a
solution that solves my immediate problem

Great!

(though I am still interested
in the "right way" that keeps the infomration with the classes
themselves).

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

···

2008/8/21 Sam Hendley <sam.hendley@plymouthsystems.com>: