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