Dynamically generating classes?

Hi,

In Python I can do this:

>>> def create_class(name):
... 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
...
>>> MyClass = create_class("MyClass")
>>>
>>> obj = MyClass(value=10)
>>> print obj.value
10

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

--Jonas Galvez

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

···

On Wed, 28 Sep 2005, Jonas Galvez wrote:

Hi,

In Python I can do this:

def create_class(name):

... 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
...

MyClass = create_class("MyClass")

obj = MyClass(value=10)
print obj.value

10

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

--

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

===============================================================================

Jonas Galvez wrote:

Hi,

In Python I can do this:

>>> def create_class(name):
... 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
...
>>> MyClass = create_class("MyClass")
>>>
>>> obj = MyClass(value=10)
>>> print obj.value
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

def create_class(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
    klass
  end

  MyClass = create_class
  obj = 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

···

On 9/27/05, Jonas Galvez <jonasgalvez@gmail.com> wrote:

In Python I can do this:
>>> def create_class(name):
... 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
...
>>> MyClass = create_class("MyClass")
>>>
>>> obj = MyClass(value=10)
>>> print obj.value
10

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

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

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

···

On Wed, Sep 28, 2005 at 06:42:57AM +0900, Jonas Galvez wrote:

In Python I can do this:

>>> def create_class(name):
... 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
...
>>> MyClass = create_class("MyClass")
>>>
>>> obj = MyClass(value=10)
>>> print obj.value
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 = Class.new do
  attr_accessor :value
  def initialize(value)
    @value = value
  end
end

Object.const_set('MyClass', klass)

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

Regards,

Sean

···

On 9/27/05, Jonas Galvez <jonasgalvez@gmail.com> wrote:

In Python I can do this:

>>> def create_class(name):
... 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
...
>>> MyClass = create_class("MyClass")
>>>
>>> obj = MyClass(value=10)
>>> print obj.value
10

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

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

-a

···

On 9/27/05, Jonas Galvez <jonasgalvez@gmail.com> wrote:

Jonas Galvez wrote:
> 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?

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

Austin Ziegler wrote:

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

Austin Ziegler wrote:

-a

A case of mistaken identity?

-devin

Austin Ziegler wrote:

Jonas Galvez wrote:

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?

  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

···

On 9/27/05, Jonas Galvez <jonasgalvez@gmail.com> wrote:

[snip]

    tostr = 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 = {
  :MyEx1 => {'code' => 1, 'message' => "Oops!"},
  :MyEx2 => {'code' => 2, 'message' => "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 = MyEx1.new
p o

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

HTH,

Sean

No, both Ara and I will sign with -a from time to time :wink:

-austin

···

On 9/28/05, Devin Mullins <twifkak@comcast.net> wrote:

Austin Ziegler wrote:
>-a
A case of mistaken identity?

--
Austin Ziegler * halostatue@gmail.com
               * Alternate: austin@halostatue.ca

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

Sean O'Halpin wrote:
> 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

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

This works as expected:

ERROR_CODES = {
:MyEx1 => {'code' => 1, 'message' => "Oops!"},
:MyEx2 => {'code' => 2, 'message' => "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

···

On 9/28/05, Jonathan Yurek <jyurek@thoughtbot.com> wrote:

[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