Class: RecursiveOpenStruct

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

Overview

rubocop:disable Metrics/AbcSize rubocop:disable Metrics/CyclomaticComplexity rubocop:disable Metrics/PerceivedComplexity rubocop:disable Style/StringConcatenation

Defined Under Namespace

Modules: DebugInspect, Dig Classes: DeepDup

Constant Summary collapse

VERSION =
'2.1.1'

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DebugInspect

#debug_inspect, #display_recursive_open_struct

Methods included from Dig

#dig

Constructor Details

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

rubocop:disable Lint/MissingSuper Intentionally doesn’t call super and initializes @table itself.



36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/recursive_open_struct.rb', line 36

def initialize(hash = nil, passed_options = {})
  hash = hash.to_h if [hash.is_a?(RecursiveOpenStruct), hash.is_a?(OpenStruct)].any?
  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

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. rubocop:disable Metrics/AbcSize rubocop:disable Metrics/PerceivedComplexity



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/recursive_open_struct.rb', line 131

def method_missing(mid, *args)
  len = args.length
  if mid =~ /^(.*)=$/
    raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) if len != 1

    # self[$1.to_sym] = args[0]
    # modifiable?[new_ostruct_member!($1.to_sym)] = args[0]
    new_ostruct_member!(::Regexp.last_match(1).to_sym)
    public_send(mid, args[0])
  elsif len.zero?
    key = mid
    key = ::Regexp.last_match(1) if key =~ /^(.*)_as_a_hash$/
    if @table.key?(_get_key_from_table_(key))
      new_ostruct_member!(key)
      public_send(mid)
    elsif @options[:raise_on_missing]
      err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
      err.set_backtrace caller(1)
      raise err
    end
  else
    err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
    err.set_backtrace caller(1)
    raise err
  end
end

Class Method Details

.default_optionsObject



25
26
27
28
29
30
31
32
# File 'lib/recursive_open_struct.rb', line 25

def self.default_options
  {
    mutate_input_hash: false,
    recurse_over_arrays: false,
    preserve_original_keys: false,
    raise_on_missing: false
  }
end

Instance Method Details

#[](name) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/recursive_open_struct.rb', line 93

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) && @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) ⇒ Object



107
108
109
110
111
112
# File 'lib/recursive_open_struct.rb', line 107

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) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/recursive_open_struct.rb', line 195

def delete_field(name)
  sym = _get_key_from_table_(name)
  begin
    singleton_class.__send__(:remove_method, sym, "#{sym}=")
  rescue StandardError
    # ignore if methods not yet generated.
    NoMethodError
  end
  @sub_elements.delete(sym)
  @table.delete(sym)
end

#freezeObject

rubocop:enable Metrics/PerceivedComplexity rubocop:enable Metrics/AbcSize



160
161
162
163
164
165
166
# File 'lib/recursive_open_struct.rb', line 160

def freeze
  @table.each_key do |key|
    new_ostruct_member!(key)
  end

  super
end

#initialize_copy(orig) ⇒ Object



62
63
64
65
66
67
68
69
# File 'lib/recursive_open_struct.rb', line 62

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

#marshal_dumpObject



57
58
59
# File 'lib/recursive_open_struct.rb', line 57

def marshal_dump
  [super, @options]
end

#marshal_load(attributes) ⇒ Object

rubocop:enable Lint/MissingSuper



50
51
52
53
54
55
# File 'lib/recursive_open_struct.rb', line 50

def marshal_load(attributes)
  hash, @options = attributes
  @deep_dup = DeepDup.new(@options)
  @sub_elements = {}
  super(hash)
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.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/recursive_open_struct.rb', line 170

def new_ostruct_member(name)
  key_name = _get_key_from_table_(name)
  unless 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

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

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

Returns:

  • (Boolean)


122
123
124
125
# File 'lib/recursive_open_struct.rb', line 122

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



72
73
74
# File 'lib/recursive_open_struct.rb', line 72

def to_h
  @deep_dup.call(@table)
end