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