Alternatives to class variables

S

Sam Hendley

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
 
P

Pit Capitain

2008/8/20 Sam Hendley said:
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 :)

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
 
S

Sam Hendley

Pit said:
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

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
 
P

Pit Capitain

2008/8/21 Sam Hendley said:
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
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top