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
> 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()?
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
===============================================================================
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
--
Austin Ziegler * halos...@gmail.com
* Alternate: aus...@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
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
> 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
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
> 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
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
>-a
>
>
A case of mistaken identity?
-devin
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
[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
No, both Ara and I will sign with -a from time to time ;)
-austin