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
# 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
# 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
#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
# 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
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
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
Makes sure ROS responds as expected on respond_to? and method requests
# 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
# File lib/recursive_open_struct.rb, line 41 def to_h @deep_dup.call(@table) end
Private Instance Methods
# 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
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.
# 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