From: Mauricio Fernandez Date: 2006-06-01T20:54:18+09:00 Subject: An alternative to the class Foo < Struct.new(vars) idiom and SuperStruct 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 # => # 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 # => # 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 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