#-- # OpenObject # # Copyright (c) 2005 Thomas Sawyer & George Moschovitis # # Ruby License # # This module is free software. You may use, modify, and/or redistribute this # software under the same terms as Ruby. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # #====================================================================== # Revision History #====================================================================== # 2005-09-01 trans # Created as a more robust alternative to OpenStruct. #====================================================================== #++ # :title: OpenObject # # OpenObject is similar to OpenStruct, but differs in a few # significant ways. # # OpenObject is a sublcass of BasicObject, so there are # many fewer reserved Kernel methods in the object's namespace. # # Also it does not create any instance methods for the added entries, # but rather strictly uses a hash. OpenStruct creates methods, but is # actaully inconsitant in their use --try #dup on an OpenStruct and # all the methods disappear. Morevoer, I beleive this feature was added # to OpenStruct for speed. But I have not been able to find this # advantage in benchmark tests. # #-- # Thrid, when an entry is not found 'null' is returned rather then 'nil'. # This allows for run-on entries withuot error. Eg. # # OpenObject.new.a.b.c #=> null #++ # # == Author(s) # # * George Moschovitis # * Thomas Sawyer # require 'facet/basicobject' #require 'facet/nullclass' class OpenObject < BasicObject def initialize( init=nil ) case init when Array @table = Hash[*init] when Hash @table = init.dup when NilClass @table = {} when OpenObject @table = init.__table__.dup else @table = init.to_h.dup end end def to_a ; @table.values ; end def to_h ; @table.dup ; end def to_hash ; @table.dup ; end # duplicate def initialize_copy( orig ) super @table = @table.dup end def inspect s = "<#{__class__}" s << " " << @table.collect{|k,v| "#{k}=#{v.inspect}"}.join(' ') unless @table.empty? s << ">" end def marshal_dump @table end def marshal_load( tbl ) @table = tbl end # Compare this object and +other+ for equality. def ==(other) return false unless other.kind_of?( __class__ ) return @table == other.__table__ end def method_missing( sym, *args ) type = sym.to_s[-1,1] name = sym.to_s.gsub(/[=!?]$/, '').to_sym if type == '=' @table[name] = args[0] elsif type == '!' and args.size > 0 @table[name] = args[0] self else #if @table.key?(name) @table[name] #else # Kernel.null #@table[name] = instance.class.new #end end end def [](name) name = name.to_sym #if @table.key?(name) @table[name] #else # Kernel.null #end end def []=(name,val) @table[name.to_sym] = val end # For compatibility with other types of hash-like objects. def each(&yld) @table.each( &yld ) end def __table__ ; @table ; end #def __get__(k) ; @table.key?(k.to_sym) ? @table[k.to_sym] : null ; end def __get__(k) ; @table[k.to_sym] ; end def __set__(k) ; @table[k.to_sym]=v ; end def __key__?(key) ; @table.key?(key) ; end def __keys__ ; @table.keys ; end def __update__( other ) other = Hash[*other] if other.is_a?(Array) other.each{ |k, v| @table[k] = v } end def __merge__( other ) o = dup o.__update__( other ) o end end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin testing require 'test/unit' class TC01 < Test::Unit::TestCase def test_01_001 f0 = OpenObject.new f0[:a] = 1 assert_equal( [1], f0.to_a ) assert_equal( {:a=>1}, f0.to_h ) end def test_01_002 f0 = OpenObject.new( {:a=>1} ) f0[:b] = 2 assert_equal( {:a=>1,:b=>2}, f0.to_h ) end end class TC02 < Test::Unit::TestCase def test_02_001 f0 = OpenObject.new( :f0=>"f0" ) h0 = { :h0=>"h0" } assert_equal( OpenObject.new( :f0=>"f0", :h0=>"h0" ), f0.__merge__( h0 ) ) assert_equal( {:f0=>"f0", :h0=>"h0"}, h0.merge( f0 ) ) end def test_02_002 f1 = OpenObject.new( :f1=>"f1" ) h1 = { :h1=>"h1" } f1.__update__( h1 ) h1.update( f1 ) assert_equal( OpenObject.new( :f1=>"f1", :h1=>"h1" ), f1 ) assert_equal( {:f1=>"f1", :h1=>"h1"}, h1 ) end end class TC03 < Test::Unit::TestCase def test_01_001 fo = OpenObject.new 99.times{ |i| fo.__send__( "n#{i}=", 1 ) } 99.times{ |i| assert_equal( 1, fo.__send__( "n#{i}" ) ) } end end =end