summaryrefslogtreecommitdiff
path: root/lib/ostruct.rb
blob: 6af5bbdac0a2406e6eac575b45ebfbf46c0db8ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#
# = ostruct.rb: OpenStruct implementation
#
# Author:: Yukihiro Matsumoto
# Documentation:: Gavin Sinclair
#
# OpenStruct allows the creation of data objects with arbitrary attributes.
# See OpenStruct for an example.
#

#
# OpenStruct allows you to create data objects and set arbitrary attributes.
# For example:
#
#   require 'ostruct' 
#
#   record = OpenStruct.new
#   record.name    = "John Smith"
#   record.age     = 70
#   record.pension = 300
#   
#   puts record.name     # -> "John Smith"
#   puts record.address  # -> nil
#
# It is like a hash with a different way to access the data.  In fact, it is
# implemented with a hash, and you can initialize it with one.
#
#   hash = { "country" => "Australia", :population => 20_000_000 }
#   data = OpenStruct.new(hash)
#
#   p data        # -> <OpenStruct country="Australia" population=20000000>
#
class OpenStruct
  #
  # Create a new OpenStruct object.  The optional +hash+, if given, will
  # generate attributes and values.  For example.
  #
  #   require 'ostruct'
  #   hash = { "country" => "Australia", :population => 20_000_000 }
  #   data = OpenStruct.new(hash)
  #
  #   p data        # -> <OpenStruct country="Australia" population=20000000>
  #
  # By default, the resulting OpenStruct object will have no attributes. 
  #
  def initialize(hash=nil)
    @table = {}
    if hash
      for k,v in hash
        @table[k.to_sym] = v
        new_ostruct_member(k)
      end
    end
  end

  # Duplicate an OpenStruct object members. 
  def initialize_copy(orig)
    super
    @table = @table.dup
  end

  def marshal_dump
    @table
  end
  def marshal_load(x)
    @table = x
    @table.each_key{|key| new_ostruct_member(key)}
  end

  def new_ostruct_member(name)
    name = name.to_sym
    unless self.respond_to?(name)
      meta = class << self; self; end
      meta.send(:define_method, name) { @table[name] }
      meta.send(:define_method, :"#{name}=") { |x| @table[name] = x }
    end
  end

  def method_missing(mid, *args) # :nodoc:
    mname = mid.id2name
    len = args.length
    if mname =~ /=$/
      if len != 1
        raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
      end
      if self.frozen?
        raise TypeError, "can't modify frozen #{self.class}", caller(1)
      end
      mname.chop!
      self.new_ostruct_member(mname)
      @table[mname.intern] = args[0]
    elsif len == 0
      @table[mid]
    else
      raise NoMethodError, "undefined method `#{mname}' for #{self}", caller(1)
    end
  end

  #
  # Remove the named field from the object.
  #
  def delete_field(name)
    @table.delete name.to_sym
  end

  InspectKey = :__inspect_key__ # :nodoc:

  #
  # Returns a string containing a detailed summary of the keys and values.
  #
  def inspect
    str = "#<#{self.class}"

    Thread.current[InspectKey] ||= []
    if Thread.current[InspectKey].include?(self) then
      str << " ..."
    else
      first = true
      for k,v in @table
        str << "," unless first
        first = false

        Thread.current[InspectKey] << v
        begin
          str << " #{k}=#{v.inspect}"
        ensure
          Thread.current[InspectKey].pop
        end
      end
    end

    str << ">"
  end
  alias :to_s :inspect

  attr_reader :table # :nodoc:
  protected :table

  #
  # Compare this object and +other+ for equality.
  #
  def ==(other)
    return false unless(other.kind_of?(OpenStruct))
    return @table == other.table
  end
end