I wrote this a couple weeks ago
( http://eigenclass.org/hiki.rb?struct-alike+class+definition ) and was
considering whether it deserves an actual release. The implementation is
simple; taking just 40 lines of code, it allows you to do stuff like
class MyClass < SuperClass.new(:a, :b) # doubly appropriate :)
def sum
@a + @b
end
end
a = MyClass.new(1, 1)
a # => #<MyClass:0xb7dfd254 @b=1, @a=1>
a.sum # => 2
The generated class uses normal instance variables unlike Struct.new, and
creates the accessors for you.
You also get keyword arguments for free:
b = MyClass.new :a => 1, :b => 41
b.sum # => 42
b # => #<MyClass:0xb7dfd024 @b=41, @a=1>
b.b # => 41
Default values are handled as follows:
class Foo < SuperClass.new(:text, :times) { @times ||= 2 }
def greeting
(["Hello, #{@text}"] * times).join("\n")
end
end
Foo.new("SuperClass", 2).greeting # => "Hello, SuperClass\nHello, SuperClass"
Foo.new(:text => "world").greeting # => "Hello, world\nHello, world"
Unlike Struct.new, you can use SuperClass to generate classes in the middle
of the inheritance chain:
class X
attr_reader :foo
def initialize(foo = 1)
@foo = foo
end
end
class Y < SuperClass.new(:bar, :baz, X) {@baz ||= 10; initialize_super(@baz + 1) }
def fubar; @foo + @baz end
end
Y.new(10, 1).foo # => 2
Y.new(:bar => 1).fubar # => 21
I have an extended implementation that also creates #eql?, #hash and #==
methods with selectable semantics.
Here's the basic implementation:
# Copyright (C) 2006 Mauricio Fernandez <mfp / acm.org> http://eigenclass.org
# Use and distribution under the same terms as Ruby.
class SuperClass
def self.new_class(accessor_type, *args, &block)
parent = args.pop if Class === args.last
parent ||= Object
unless args.size > 1
raise ArgumentError, "No point in using SuperClass for a single argument!"
end
Class.new(parent) do
@initialize_args = args.map{|x| "@#{x}".intern}
class << self; attr_reader :initialize_args end
case accessor_type
when :ro : attr_reader(*args)
when :rw : attr_accessor(*args)
end
define_method(:initialize) do |*a|
args.each{|name| instance_variable_set("@#{name}", nil) }
if a.size == 1 && Hash === a[0]
args.each{|name| instance_variable_set("@#{name}", a[0][name.to_sym])}
elsif a.size != args.size
raise ArgumentError,
"wrong number of arguments (#{a.size} for #{args.size})"
else
args.each_with_index{|name, i| instance_variable_set("@#{name}", a[i])}
end
instance_eval(&block) if block
end
if block
super_meth = parent.instance_method(:initialize)
define_method(:initialize_super){|*a| super_meth.bind(self).call(*a) }
private :initialize_super
end
end
end
def self.new(*args, &block); new_class(:ro, *args, &block) end
def self.new_rw(*args, &block); new_class(:rw, *args, &block) end
end
--
Mauricio Fernandez - http://eigenclass.org - singular Ruby