Class: RecursiveOpenStruct

Inherits:
OpenStruct
  • Object
show all
Includes:
DebugInspect, Dig, Ruby19Backport
Defined in:
lib/recursive_open_struct.rb,
lib/recursive_open_struct/dig.rb,
lib/recursive_open_struct/version.rb

Overview

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

Defined Under Namespace

Modules: DebugInspect, Dig, Ruby19Backport Classes: DeepDup

Constant Summary collapse

VERSION =
"1.1.0"

Instance Method Summary collapse

Methods included from DebugInspect

#debug_inspect, #display_recursive_open_struct

Methods included from Dig

#dig

Methods included from Ruby19Backport

#[]=, #each_pair, #eql?, #hash

Constructor Details

#initialize(hash = nil, args = {}) ⇒ RecursiveOpenStruct

Returns a new instance of RecursiveOpenStruct.



18
19
20
21
22
23
24
25
26
27
28
29
30
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(mid, *args) ⇒ Object

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.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# 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

Instance Method Details

#[](name) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 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) ⇒ Object



125
126
127
128
129
130
# 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) ⇒ Object



32
33
34
35
36
37
38
39
# 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

#new_ostruct_member(name) ⇒ Object Also known as: new_ostruct_member!

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



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# 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

#respond_to_missing?(mid, include_private = false) ⇒ Boolean

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

Returns:

  • (Boolean)


66
67
68
69
# 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_hObject Also known as: to_hash



41
42
43
# File 'lib/recursive_open_struct.rb', line 41

def to_h
  @deep_dup.call(@table)
end