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

TODO: `#*_as_a_hash` deprecated. Nested hashes can be referenced using `#to_h`.

Constants

VERSION

Public Class Methods

default_options() click to toggle source
# File lib/recursive_open_struct.rb, line 22
def self.default_options
  {
    mutate_input_hash: false,
    recurse_over_arrays: false,
    preserve_original_keys: false
  }
end
new(hash=nil, passed_options={}) click to toggle source
# File lib/recursive_open_struct.rb, line 30
def initialize(hash=nil, passed_options={})
  hash ||= {}

  @options = self.class.default_options.merge!(passed_options).freeze

  @deep_dup = DeepDup.new(@options)

  @table = @options[:mutate_input_hash] ? hash : @deep_dup.call(hash)

  @sub_elements = {}
end

Public Instance Methods

[](name) click to toggle source
# File lib/recursive_open_struct.rb, line 75
def [](name)
  key_name = _get_key_from_table_(name)
  v = @table[key_name]
  if v.is_a?(Hash)
    @sub_elements[key_name] ||= _create_sub_element_(v, mutate_input_hash: true)
  elsif v.is_a?(Array) and @options[: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
[]=(name, value) click to toggle source
# File lib/recursive_open_struct.rb, line 89
def []=(name, value)
  key_name = _get_key_from_table_(name)
  tbl = modifiable?  # Ensure we are modifiable
  @sub_elements.delete(key_name)
  tbl[key_name] = value
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 162
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
Calls superclass method
# File lib/recursive_open_struct.rb, line 44
def initialize_copy(orig)
  super

  # deep copy the table to separate the two objects
  @table = @deep_dup.call(@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.

# File lib/recursive_open_struct.rb, line 111
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
    # self[$1.to_sym] = args[0]
    # modifiable?[new_ostruct_member!($1.to_sym)] = args[0]
    new_ostruct_member!($1.to_sym)
    public_send(mid, 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)
      public_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 137
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|
        self[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 104
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 54
def to_h
  @deep_dup.call(@table)
end
Also aliased as: to_hash
to_hash()

TODO: deprecated, unsupported by OpenStruct. OpenStruct does not consider itself to be a “kind of” Hash.

Alias for: to_h

Private Instance Methods

_create_sub_element_(hash, **overrides) click to toggle source
# File lib/recursive_open_struct.rb, line 187
def _create_sub_element_(hash, **overrides)
  self.class.new(hash, @options.merge(overrides))
end
_get_key_from_table_(name) click to toggle source
# File lib/recursive_open_struct.rb, line 181
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
initialize_dup(orig) click to toggle source
Calls superclass method
# File lib/recursive_open_struct.rb, line 172
def initialize_dup(orig)
  super
  # deep copy the table to separate the two objects
  @table = @deep_dup.call(@table)
  # Forget any memoized sub-elements
  @sub_elements = {}
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 191
def recurse_over_array(array)
  array.each_with_index do |a, i|
    if a.is_a? Hash
      array[i] = _create_sub_element_(a, mutate_input_hash: true, recurse_over_arrays: true)
    elsif a.is_a? Array
      array[i] = recurse_over_array a
    end
  end
  array
end