class OpenStruct2

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.

Public Class Methods

auto(data=nil) click to toggle source

Create autovivified OpenStruct.

@example

o = OpenStruct2.renew
o.a  #=> #<OpenStruct2: {}>
# File lib/ostruct2.rb, line 45
def auto(data=nil)
  leet = lambda{ |h,k| new(&leet) }
  new(&leet)
end
Also aliased as: renew
cascade(data=nil) click to toggle source

Constructor that is both autovivified and nested.

# File lib/ostruct2.rb, line 76
def cascade(data=nil)
  o = renew
  o.nested!(true)
  o.update!(data) if data
  o
end
nest(data=nil)

Shorter name for ‘nested`.

Alias for: nested
nested(data=nil) click to toggle source

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

# File lib/ostruct2.rb, line 61
def nested(data=nil)
  o = new
  o.nested!(true)
  o.update!(data) if data
  o
end
Also aliased as: nest
new(data=nil, &block) click to toggle source

Initialize new instance of OpenStruct.

@param [Hash] data

# File lib/ostruct2.rb, line 95
def initialize(data=nil, &block)
  @table = ::Hash.new(&block)
  update!(data || {})
end
renew(data=nil)

Another name for auto method.

TODO: Still wondering waht the best name is for this.

Alias for: auto

Private Class Methods

const_missing(name) click to toggle source
# File lib/ostruct2.rb, line 85
def const_missing(name)
  ::Object.const_get(name)
end

Public Instance Methods

==(other) click to toggle source

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?

# File lib/ostruct2.rb, line 390
def ==(other)
  return false unless(other.kind_of?(__class__))
  return @table == other.table #to_h
end
[](key) click to toggle source

Alias for ‘#read!`.

@param [#to_sym] key

@return [Object]

# File lib/ostruct2.rb, line 234
def [](key)
  read!(key)
end
[]=(key, value) click to toggle source

Alias for ‘#store!`.

@param [#to_sym] key

@param [Object] value

@return value.

# File lib/ostruct2.rb, line 247
def []=(key, value)
  store!(key, value)
end
__class__() click to toggle source

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

# File lib/ostruct2.rb, line 104
def __class__
  OpenStruct2
end
clone()
Alias for: dup
delete!(key) click to toggle source

The CRUD method for destroy.

# File lib/ostruct2.rb, line 191
def delete!(key)
  @table.delete(key.to_sym)
end
delete_field(key) click to toggle source

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

@deprecated Use ‘#delete!` method instead.

# File lib/ostruct2.rb, line 201
def delete_field(key)
  @table.delete(key.to_sym)
end
dup() click to toggle source

Duplicate OpenStruct object.

@return [OpenStruct] Duplicate instance.

# File lib/ostruct2.rb, line 339
def dup
  __class__.new(@table, &@table.default_proc)
end
Also aliased as: clone
each!() { |key, read!(key)| ... } click to toggle source

CRUDified each.

@return nothing

# File lib/ostruct2.rb, line 256
def each!
  @table.each_key do |key|
    yield(key, read!(key))
  end
end
empty?() click to toggle source

Is the OpenStruct void of entries?

# File lib/ostruct2.rb, line 371
def empty?
  @table.empty?
end
eql?(other) click to toggle source

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

# 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) click to toggle source

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

# File lib/ostruct2.rb, line 208
def fetch!(key)
  key!(key)
  read!(key)
end
freeze() click to toggle source

Freeze OpenStruct instance.

# File lib/ostruct2.rb, line 355
def freeze
  @table.freeze
end
frozen?() click to toggle source

Is the OpenStruct instance frozen?

@return [Boolean]

# File lib/ostruct2.rb, line 364
def frozen?
  @table.frozen?
end
hash() click to toggle source

Hash number.

# File lib/ostruct2.rb, line 348
def hash
  @table.hash
end
initialize_copy(original) click to toggle source

Duplicate underlying table when OpenStruct is duplicated or cloned.

@param [OpenStruct] original

Calls superclass method
# File lib/ostruct2.rb, line 113
def initialize_copy(original)
  super
  @table = @table.dup
end
inspect() click to toggle source

Inspect OpenStruct object.

@return [String]

# File lib/ostruct2.rb, line 301
def inspect
  "#<#{__class__}: #{@table.inspect}>"
end
Also aliased as: to_s
key!(key) click to toggle source

If key is not present raise a KeyError.

@param [#to_sym] key

@raise [KeyError] If key is not present.

@return key

# 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) click to toggle source

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

# File lib/ostruct2.rb, line 164
def key?(key)
  @table.key?(key.to_sym)
end
keys!() click to toggle source

CRUD method for listing all keys.

# File lib/ostruct2.rb, line 157
def keys!
  @table.keys
end
map!(&block) click to toggle source
# File lib/ostruct2.rb, line 263
def map!(&block)
  to_enum.map(&block)
end
merge!(other) click to toggle source

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`.

@return [OpenStruct]

# File lib/ostruct2.rb, line 288
def merge!(other)
  o = dup
  other.each do |k,v|
    o.store!(k,v)
  end
  o
end
method_missing(sym, *args, &blk) click to toggle source

Dispatch unrecognized member calls.

# 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
nested!(boolean=nil) click to toggle source

Get/set nested flag.

# File lib/ostruct2.rb, line 146
def nested!(boolean=nil)
  if boolean.nil?
    @nested
  else
    @nested = !!boolean
  end
end
read!(key) click to toggle source

The CRUD method for read.

# File lib/ostruct2.rb, line 171
def read!(key)
  @table[key.to_sym]
end
store!(key, value) click to toggle source

The CRUD method for create and update.

# 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!) click to toggle source

Create an enumerator based on ‘#each!`.

@return [Enumerator]

# 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_h() click to toggle source

Get a duplicate of the underlying table.

@return [Hash]

# File lib/ostruct2.rb, line 312
def to_h
  @table.dup
end
to_s()
Alias for: inspect
update!(other) click to toggle source

CRUDified update method.

@return [self]

# File lib/ostruct2.rb, line 272
def update!(other)
  other.each do |k,v|
    store!(k,v)
  end
  self
end

Protected Instance Methods

new_ostruct_member(name) click to toggle source
# File lib/ostruct2.rb, line 401
def new_ostruct_member(name)
  name = name.to_sym
  # TODO: Check `#respond_to?` is needed? And if so how to do this in BasicObject?
  #return name if self.respond_to?(name)
  (class << self; self; end).class_eval do
    define_method(name) { read!(name) }
    define_method("#{name}?") { key?(name) }
    define_method("#{name}=") { |value| store!(name, value) }
  end
  name
end
table() click to toggle source
# File lib/ostruct2.rb, line 397
def table
  @table
end