class RecursiveOpenStruct

TODO: When we care less about Rubies before 2.4.0, match OpenStruct's method names instead of doing things like aliasing `new_ostruct_member` to `new_ostruct_member!`

Constants

VERSION

Public Class Methods

new(hash=nil, args={}) click to toggle source
# File lib/recursive_open_struct.rb, line 18
def initialize(hash=nil, args={})
  hash ||= {}
  @recurse_over_arrays = args.fetch(:recurse_over_arrays, false)
  @preserve_original_keys = args.fetch(:preserve_original_keys, false)
  @deep_dup = DeepDup.new(
    recurse_over_arrays: @recurse_over_arrays,
    preserve_original_keys: @preserve_original_keys
  )

  @table = args.fetch(:mutate_input_hash, false) ? hash : @deep_dup.call(hash)

  @sub_elements = {}
end

Public Instance Methods

[](name) click to toggle source
# File lib/recursive_open_struct.rb, line 47
def [](name)
  key_name = _get_key_from_table_(name)
  v = @table[key_name]
  if v.is_a?(Hash)
    @sub_elements[key_name] ||= self.class.new(
      v,
      recurse_over_arrays: @recurse_over_arrays,
      preserve_original_keys: @preserve_original_keys,
      mutate_input_hash: true
    )
  elsif v.is_a?(Array) and @recurse_over_arrays
    @sub_elements[key_name] ||= recurse_over_array(v)
    @sub_elements[key_name] = recurse_over_array(@sub_elements[key_name])
  else
    v
  end
end
delete_field(name) click to toggle source

#new_ostruct_member! is private, but #new_ostruct_member is not on OpenStruct in 2.4.0-rc1?!

# File lib/recursive_open_struct.rb, line 125
def delete_field(name)
  sym = _get_key_from_table_(name)
  singleton_class.__send__(:remove_method, sym, "#{sym}=") rescue NoMethodError # ignore if methods not yet generated.
  @sub_elements.delete sym
  @table.delete sym
end
initialize_copy(orig) click to toggle source
# File lib/recursive_open_struct.rb, line 32
def initialize_copy(orig)
  super

  # deep copy the table to separate the two objects
  @table = @deep_dup.call(orig.instance_variable_get(:@table))
  # Forget any memoized sub-elements
  @sub_elements = {}
end
method_missing(mid, *args) click to toggle source

Adapted implementation of #method_missing to accommodate the differences between ROS and OS.

TODO: Use modifiable? instead of modifiable, and #new_ostruct_member! instead of #new_ostruct_member once we care less about Rubies before 2.4.0.

# File lib/recursive_open_struct.rb, line 76
def method_missing(mid, *args)
  len = args.length
  if mid =~ /^(.*)=$/
    if len != 1
      raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
    end
    modifiable[new_ostruct_member!($1.to_sym)] = args[0]
  elsif len == 0
    key = mid
    key = $1 if key =~ /^(.*)_as_a_hash$/
    if @table.key?(_get_key_from_table_(key))
      new_ostruct_member!(key)
      send(mid)
    end
  else
    err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
    err.set_backtrace caller(1)
    raise err
  end
end
new_ostruct_member(name) click to toggle source

TODO: Rename to #new_ostruct_member! once we care less about Rubies before 2.4.0.

# File lib/recursive_open_struct.rb, line 99
def new_ostruct_member(name)
  key_name = _get_key_from_table_(name)
  unless self.singleton_class.method_defined?(name.to_sym)
    class << self; self; end.class_eval do
      define_method(name) do
        self[key_name]
      end
      define_method("#{name}=") do |x|
        @sub_elements.delete(key_name)
        modifiable[key_name] = x
      end
      define_method("#{name}_as_a_hash") { @table[key_name] }
    end
  end
  key_name
end
Also aliased as: new_ostruct_member!
respond_to_missing?(mid, include_private = false) click to toggle source

Makes sure ROS responds as expected on respond_to? and method requests

Calls superclass method
# File lib/recursive_open_struct.rb, line 66
def respond_to_missing?(mid, include_private = false)
  mname = _get_key_from_table_(mid.to_s.chomp('=').chomp('_as_a_hash'))
  @table.key?(mname) || super
end
to_h() click to toggle source
# File lib/recursive_open_struct.rb, line 41
def to_h
  @deep_dup.call(@table)
end
Also aliased as: to_hash
to_hash()
Alias for: to_h

Private Instance Methods

_get_key_from_table_(name) click to toggle source
# File lib/recursive_open_struct.rb, line 134
def _get_key_from_table_(name)
  return name.to_s if @table.has_key?(name.to_s)
  return name.to_sym if @table.has_key?(name.to_sym)
  name
end
new_ostruct_member!(name)

Support Ruby 2.4.0+'s changes in a way that doesn't require dynamically modifying ROS.

TODO: Once we care less about Rubies before 2.4.0, reverse this so that #new_ostruct_member points to our version and not OpenStruct's.

Alias for: new_ostruct_member
recurse_over_array(array) click to toggle source
# File lib/recursive_open_struct.rb, line 140
def recurse_over_array(array)
  array.each_with_index do |a, i|
    if a.is_a? Hash
      array[i] = self.class.new(a, :recurse_over_arrays => true,
        :mutate_input_hash => true, :preserve_original_keys => @preserve_original_keys)
    elsif a.is_a? Array
      array[i] = recurse_over_array a
    end
  end
  array
end