Class: OpenStruct2

Inherits:
BasicObject
Defined in:
lib/ostruct2.rb

Overview

OpenStruct2 is a better OpenStruct class.

To demonstrate the weakness of the original OpenStruct, try this IRB session:

irb(main):001:0> o = OpenStruct.new
=> #<OpenStruct>
irb(main):002:0> o.display = "Hello, World!"
=> "Hello, World!"
irb(main):003:0> o.display
#<OpenStruct display="Hello, World!">=> nil

This new OpenStruct class allows almost any member name to be used. The only exceptions are methods starting with double underscores, such as ‘__id__` and `__send__`, and a few neccessary public methods: `clone`, `dup`, `freeze`, `hash`, `to_enum`, `to_h`, `to_s` and `inspect`, as well as `instance_eval` and `instance_exec`.

Also note that ‘empty`, `eql`, `equal`, `frozen` and `key` can be used as members but the key-check shorthand of using `?`-methods cannot be used since these have special definitions.

To offset the loss of most methods, OpenStruct provides numerous bang-methods which can be used to manipulate the data, e.g. ‘#each!`. Currently most bang-methods route directly to the underlying hash table, so developers should keep that in mind when using this feature. A future version may add an intermediate interface to always ensure proper “CRUD”, functonality but in the vast majority of cases it will make no difference, so it is left for later consideration.

This improved version of OpenStruct also has no issues with being cloned since it does not depend on singleton methods to work. But singleton methods are used to help boost performance. But instead of always creating singleton methods, it only creates them on the first attempt to use them.

Direct Known Subclasses

OpenStruct

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = nil, &block) ⇒ OpenStruct2

Initialize new instance of OpenStruct.

Parameters:

  • data (Hash) (defaults to: nil)


95
96
97
98
# File 'lib/ostruct2.rb', line 95

def initialize(data=nil, &block)
  @table = ::Hash.new(&block)
  update!(data || {})
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &blk) ⇒ Object

Dispatch unrecognized member calls.



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/ostruct2.rb', line 121

def method_missing(sym, *args, &blk)
  str  = sym.to_s
  type = str[-1,1]
  name = str.chomp('=').chomp('!').chomp('?')

  case type
  when '!'
    # TODO: Probably should have an indirect interface to ensure proper
    #       functonality in all cases.
    @table.public_send(name, *args, &blk)
  when '='
    new_ostruct_member(name)
    store!(name, args.first)
  when '?'
    new_ostruct_member(name)
    key?(name)
  else
    new_ostruct_member(name)
    read!(name)
  end
end

Class Method Details

.auto(data = nil) ⇒ Object Also known as: renew

Create autovivified OpenStruct.

Examples:

o = OpenStruct2.renew
o.a  #=> #<OpenStruct2: {}>


45
46
47
48
# File 'lib/ostruct2.rb', line 45

def auto(data=nil)
  leet = lambda{ |h,k| new(&leet) }
  new(&leet)
end

.cascade(data = nil) ⇒ Object

Constructor that is both autovivified and nested.



76
77
78
79
80
81
# File 'lib/ostruct2.rb', line 76

def cascade(data=nil)
  o = renew
  o.nested!(true)
  o.update!(data) if data
  o
end

.nested(data = nil) ⇒ Object Also known as: nest

Create a nested OpenStruct, such that all sub-hashes added to the table also become OpenStruct objects.



61
62
63
64
65
66
# File 'lib/ostruct2.rb', line 61

def nested(data=nil)
  o = new
  o.nested!(true)
  o.update!(data) if data
  o
end

Instance Method Details

#==(other) ⇒ Object

Two OpenStructs are equal if they are the same class and their underlying tables are equal.

TODO: Why not equal for other hash types, e.g. via #to_h?



390
391
392
393
# File 'lib/ostruct2.rb', line 390

def ==(other)
  return false unless(other.kind_of?(__class__))
  return @table == other.table #to_h
end

#[](key) ⇒ Object

Alias for ‘#read!`.

Parameters:

  • key (#to_sym)

Returns:

  • (Object)


234
235
236
# File 'lib/ostruct2.rb', line 234

def [](key)
  read!(key)
end

#[]=(key, value) ⇒ Object

Alias for ‘#store!`.

Parameters:

  • key (#to_sym)
  • value (Object)

Returns:

  • value.



247
248
249
# File 'lib/ostruct2.rb', line 247

def []=(key, value)
  store!(key, value)
end

#__class__Object

Because there is no means of getting the class via a BasicObject instance, we define such a method manually.



104
105
106
# File 'lib/ostruct2.rb', line 104

def __class__
  OpenStruct2
end

#delete!(key) ⇒ Object

The CRUD method for destroy.



191
192
193
# File 'lib/ostruct2.rb', line 191

def delete!(key)
  @table.delete(key.to_sym)
end

#delete_field(key) ⇒ Object

Deprecated.

Use ‘#delete!` method instead.

Same as ‘#delete!`. This method provides compatibility with the original OpenStruct class.



201
202
203
# File 'lib/ostruct2.rb', line 201

def delete_field(key)
  @table.delete(key.to_sym)
end

#dupOpenStruct Also known as: clone

Duplicate OpenStruct object.

Returns:



339
340
341
# File 'lib/ostruct2.rb', line 339

def dup
  __class__.new(@table, &@table.default_proc)
end

#each!Object

CRUDified each.

Returns:

  • nothing



256
257
258
259
260
# File 'lib/ostruct2.rb', line 256

def each!
  @table.each_key do |key|
    yield(key, read!(key))
  end
end

#empty?Boolean

Is the OpenStruct void of entries?

Returns:

  • (Boolean)


371
372
373
# File 'lib/ostruct2.rb', line 371

def empty?
  @table.empty?
end

#eql?(other) ⇒ Boolean

Two OpenStructs are equal if they are the same class and their underlying tables are equal.

Returns:

  • (Boolean)


379
380
381
382
# File 'lib/ostruct2.rb', line 379

def eql?(other)
  return false unless(other.kind_of?(__class__))
  return @table == other.table #to_h
end

#fetch!(key) ⇒ Object

Like #read but will raise a KeyError if key is not found.



208
209
210
211
# File 'lib/ostruct2.rb', line 208

def fetch!(key)
  key!(key)
  read!(key)
end

#freezeObject

Freeze OpenStruct instance.



355
356
357
# File 'lib/ostruct2.rb', line 355

def freeze
  @table.freeze
end

#frozen?Boolean

Is the OpenStruct instance frozen?

Returns:

  • (Boolean)


364
365
366
# File 'lib/ostruct2.rb', line 364

def frozen?
  @table.frozen?
end

#hashObject

Hash number.



348
349
350
# File 'lib/ostruct2.rb', line 348

def hash
  @table.hash
end

#initialize_copy(original) ⇒ Object

Duplicate underlying table when OpenStruct is duplicated or cloned.

Parameters:



113
114
115
116
# File 'lib/ostruct2.rb', line 113

def initialize_copy(original)
  super
  @table = @table.dup
end

#inspectString Also known as: to_s

Inspect OpenStruct object.

Returns:

  • (String)


301
302
303
# File 'lib/ostruct2.rb', line 301

def inspect
  "#<#{__class__}: #{@table.inspect}>"
end

#key!(key) ⇒ Object

If key is not present raise a KeyError.

Parameters:

  • key (#to_sym)

Returns:

  • key

Raises:

  • (KeyError)

    If key is not present.



222
223
224
225
# File 'lib/ostruct2.rb', line 222

def key!(key)
  return key if key?(key)
  ::Kernel.raise ::KeyError, ("key not found: %s" % [key.inspect])
end

#key?(key) ⇒ Boolean

Also a CRUD method like #read!, but for checking for the existence of a key.

Returns:

  • (Boolean)


164
165
166
# File 'lib/ostruct2.rb', line 164

def key?(key)
  @table.key?(key.to_sym)
end

#keys!Object

CRUD method for listing all keys.



157
158
159
# File 'lib/ostruct2.rb', line 157

def keys!
  @table.keys
end

#map!(&block) ⇒ Object



263
264
265
# File 'lib/ostruct2.rb', line 263

def map!(&block)
  to_enum.map(&block)
end

#merge!(other) ⇒ OpenStruct

Merge this OpenStruct with another OpenStruct or Hash object returning a new OpenStruct instance.

IMPORTANT! This method does not act in-place like ‘Hash#merge!`, rather it works like `Hash#merge`.

Returns:



288
289
290
291
292
293
294
# File 'lib/ostruct2.rb', line 288

def merge!(other)
  o = dup
  other.each do |k,v|
    o.store!(k,v)
  end
  o
end

#nested!(boolean = nil) ⇒ Object

Get/set nested flag.



146
147
148
149
150
151
152
# File 'lib/ostruct2.rb', line 146

def nested!(boolean=nil)
  if boolean.nil?
    @nested
  else
    @nested = !!boolean
  end
end

#read!(key) ⇒ Object

The CRUD method for read.



171
172
173
# File 'lib/ostruct2.rb', line 171

def read!(key)
  @table[key.to_sym]
end

#store!(key, value) ⇒ Object

The CRUD method for create and update.



178
179
180
181
182
183
184
185
186
# File 'lib/ostruct2.rb', line 178

def store!(key, value)
  if @nested && Hash === value  # value.respond_to?(:to_hash)
    value = OpenStruct2.new(value)
  end

  #new_ostruct_member(key)  # this is here only for speed bump

  @table[key.to_sym] = value
end

#to_enum(methname = :each!) ⇒ Enumerator

Create an enumerator based on ‘#each!`.

Returns:

  • (Enumerator)


324
325
326
327
328
329
330
331
332
# File 'lib/ostruct2.rb', line 324

def to_enum(methname=:each!)
  # Why has Ruby 2 deprecated this form?
  #::Enumerator.new(self, methname) 
  ::Enumerator.new do |y|
     __send__(methname) do |*a|
       y.yield *a
     end
  end
end

#to_hHash

Get a duplicate of the underlying table.

Returns:

  • (Hash)


312
313
314
# File 'lib/ostruct2.rb', line 312

def to_h
  @table.dup
end

#update!(other) ⇒ self

CRUDified update method.

Returns:

  • (self)


272
273
274
275
276
277
# File 'lib/ostruct2.rb', line 272

def update!(other)
  other.each do |k,v|
    store!(k,v)
  end
  self
end