Dynamically generating classes?

J

Jonas Galvez

Hi,

In Python I can do this:
... import new
... c = new.classobj(name, tuple([object]), {})
... def __init__(self, value):
... self.value = value
... setattr(c, "__init__", new.instancemethod(__init__, None, c))
... return c
...10

Is there anything similar in Ruby? Or do I need to use eval()?



--Jonas Galvez
 
A

Ara.T.Howard

Hi,

In Python I can do this:
... import new
... c = new.classobj(name, tuple([object]), {})
... def __init__(self, value):
... self.value = value
... setattr(c, "__init__", new.instancemethod(__init__, None, c))
... return c
...10

Is there anything similar in Ruby? Or do I need to use eval()?

much more easily:

harp:~ > cat a.rb
klass =
Class::new {
def foo
42
end
def bar
'forty-two'
end
}

k = klass::new
p k.foo

MyKlass = klass
k = MyKlass::new
p k.bar


harp:~ > ruby a.rb
42
"forty-two"

cheers.



-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
===============================================================================
 
J

Jonas Galvez

Jonas said:
Hi,

In Python I can do this:
... import new
... c = new.classobj(name, tuple([object]), {})
... def __init__(self, value):
... self.value = value
... setattr(c, "__init__", new.instancemethod(__init__, None, c))
... return c
...10

Is there anything similar in Ruby? Or do I need to use eval()?

Hmm, apparently:

MyClass = Class.new {
attr_accessor :value
def initialize(value)
self.value = value
end
}

o = MyClass.new(10)
puts o.value

But now I ask, how do I dynamically set its name ("MyClass") in the
global namespace?

--Jonas Galvez
 
A

Austin Ziegler

In Python I can do this:... import new
... c =3D new.classobj(name, tuple([object]), {})
... def __init__(self, value):
... self.value =3D value
... setattr(c, "__init__", new.instancemethod(__init__, None, c))
... return c
...10

Is there anything similar in Ruby? Or do I need to use eval()?

def create_class(parent =3D nil)
if parent
klass =3D Class.new(parent)
else
klass =3D Class.new
end
klass.class_eval do
def initialize(value)
@value =3D value
end

attr_reader :value
end
klass
end

MyClass =3D create_class
obj =3D MyClass.new(10)
puts obj.value

The only difference is that the class's name is based on the first
constant it's given to. That's a bit of RubyMagic.

-austin
 
G

Greg Millam

In Python I can do this:
... import new
... c = new.classobj(name, tuple([object]), {})
... def __init__(self, value):
... self.value = value
... setattr(c, "__init__", new.instancemethod(__init__, None, c))
... return c
...10

Is there anything similar in Ruby? Or do I need to use eval()?

MyClass = Class.new do
attr_accessor :value
def initialize(value)
@value = value
end
end

obj = MyClass.new(10)
puts obj.value

Cleaner, I think =).

If you want "MyClass" to be dynamically set:
Obj.const_set("MyClass",Class.new { ... })

Or for really simple classes, you can use 'struct':

require 'struct'
Struct.new('MyClass','value')
obj = MyClass.new(10)
puts obj.value

Hope that helps!

- Greg
 
A

Austin Ziegler

Hmm, apparently:

MyClass =3D Class.new {
attr_accessor :value
def initialize(value)
self.value =3D value
end
}

o =3D MyClass.new(10)
puts o.value

But now I ask, how do I dynamically set its name ("MyClass") in the
global namespace?

def create_class(name, parent =3D nil)
if parent
klass =3D Class.new(parent)
else
klass =3D Class.new
end
klass.class_eval do
def initialize(value)
@value =3D value
end

attr_reader :value
end

Object.const_set(name, klass)

klass
end

create_class('MyClass')
obj =3D MyClass.new(10)
puts obj.value


-a
 
S

Sean O'Halpin

In Python I can do this:
... import new
... c =3D new.classobj(name, tuple([object]), {})
... def __init__(self, value):
... self.value =3D value
... setattr(c, "__init__", new.instancemethod(__init__, None, c))
... return c
...10

Is there anything similar in Ruby? Or do I need to use eval()?

You can create an anonymous class then associate it with a named constant:

klass =3D Class.new do
attr_accessor :value
def initialize(value)
@value =3D value
end
end

Object.const_set('MyClass', klass)

c =3D MyClass.new(10)
p c
#=3D> #<MyClass:0x2871320 @value=3D10>

Regards,

Sean
 
J

Jonas Galvez

Austin said:
Object.const_set(name, klass)

Thanks all. Here's what I'm trying to do exactly:

ERROR_CODES = YAML::load open('config/errorcodes.yml').read

for k, v in ERROR_CODES
Object.const_set(k, Class.new(Exception) {
def to_s
"{code: #{v['code']}, message: #{v['message']}}"
end
})
end

o = MyException.new # "MyExcetion" is defined in the YAML file
puts o

Unfortunately, this doesn't seem to work:

test.rb:8:in `to_s': undefined local variable or method `v' for
#<NotAFeed:0x2b6ca08> (NameError)
from test.rb:14:in `puts'
from test.rb:14

Here's the equivalent Python code (which works):

ERROR_CODES = syck.load(open('config/errorcodes.yml').read())

for k, v in ERROR_CODES.items():
nexc = new.classobj(k, (Exception,), {})
code, message = v
tostr = lambda self: "{code: %s, message: %s}" % code, message
setattr(nexc, "__str__", new.instancemethod(tostr, None, nexc))
globals()[k] = nexc

try:
raise MyException
except MyException, e:
print e # prints "{code: ..., message: ...}"


Thanks again.

--Jonas Galvez
 
S

Sean O'Halpin

[snip]
tostr =3D lambda self: "{code: %s, message: %s}" % code, message

I don't know Python's scoping rules, but to get the same effect in
Ruby you also need to use a block to capture the scope at the time of
definition - so use +define_method+ instead of +def+:

ERROR_CODES =3D {
:MyEx1 =3D> {'code' =3D> 1, 'message' =3D> "Oops!"},
:MyEx2 =3D> {'code' =3D> 2, 'message' =3D> "Whoops!"},
}

for k, v in ERROR_CODES
Object.const_set(k, Class.new(Exception) {
define_method :to_s do
"{code: #{v['code']}, message: #{v['message']}}"
end
})
end

o =3D MyEx1.new
p o

#=3D> #<MyEx1: {code: 2, message: Whoops!}>

HTH,

Sean
 
D

Devin Mullins

Here's another way you might do it:

ERROR_CODES = {
:MyEx1 => {'code' => 1, 'message' => "Oops!"},
:MyEx2 => {'code' => 2, 'message' => "Whoops!"},
}

#StandardError'd be the canonical one to inherit.
#Exceptions outside of that tree are typically Ruby errors (bad syntax,
no memory, etc.).
class MyError < StandardError
def to_s
"{code: #{CODE}, message: #{MESSAGE}}"
end
end

for k, v in ERROR_CODES
Object.const_set(k, Class.new(MyError) {
CODE = v['code']
MESSAGE = v['message']
})
end

o = MyEx1.new
p o
__END__

#<MyEx1: {code: 2, message: Whoops!}>

I think it's a little cleaner, but that's partially taste.

Devin
 
R

Robert Klemme

Austin said:
def create_class(name, parent = nil)
if parent
klass = Class.new(parent)
else
klass = Class.new
end
klass.class_eval do
def initialize(value)
@value = value
end

attr_reader :value
end

Object.const_set(name, klass)

klass
end

create_class('MyClass')
obj = MyClass.new(10)
puts obj.value

Note though that this is completely superfluous if you know the name
beforehand:

09:31:38 [Oracle]: ruby -e 'MyClass = Class.new {}; p MyClass.name'
"MyClass"

09:32:48 [Oracle]: ruby -e 'c = Class.new {}; p c.name ; MyClass = c; p
MyClass.name'
""
"MyClass"

Assigning to a constant is sufficient.

Kind regards

robert
 
J

Jonathan Yurek

Sean said:
ERROR_CODES = {
:MyEx1 => {'code' => 1, 'message' => "Oops!"},
:MyEx2 => {'code' => 2, 'message' => "Whoops!"},
}
[snip]

#<MyEx1: {code: 2, message: Whoops!}>

Isn't that wrong, though? You instantiated MyExt1, but your code and
message are displaying what MyExt2 should be.

-- Jon
 
S

Sean O'Halpin

[snip]
#<MyEx1: {code: 2, message: Whoops!}>

Isn't that wrong, though? You instantiated MyExt1, but your code and
message are displaying what MyExt2 should be.

-- Jon

Hey - you're right! It's the for vs each scoping again! Ouch!

This works as expected:

ERROR_CODES =3D {
:MyEx1 =3D> {'code' =3D> 1, 'message' =3D> "Oops!"},
:MyEx2 =3D> {'code' =3D> 2, 'message' =3D> "Whoops!"},
}

ERROR_CODES.each do |k,v|
Object.const_set(k, Class.new(Exception) {
define_method :to_s do
"{code: #{v['code']}, message: #{v['message']}}"
end
})
end

p MyEx1.new
p MyEx2.new

__END__
#<MyEx1: {code: 1, message: Oops!}>
#<MyEx2: {code: 2, message: Whoops!}>

But best not to do it this way - see Devin Mullins' post for a better way.

Sean
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top