Project

General

Profile

Feature #14555 » ostruct.rb

isiahmeadows (Isiah Meadows), 02/27/2018 12:22 AM

 
# frozen_string_literal: true
# An attempt to bring OpenStruct closer to reasonable performance.

class OpenStruct
def initialize(hash=nil)
@__table = {}
if hash
hash.each_pair do |k, v|
k = k.to_sym
add_ostruct_member!(k, v)
end
end
end

def to_h
table = {}
@__table.each_pair do |key, instance_key|
table[key] = instance_variable_get(instance_key)
end
table
end

def each_pair
return to_enum(__method__) { @__table.size } unless block_given?

@__table.each_pair do |key, instance_key|
yield key, instance_variable_get(instance_key)
end

self
end

alias :marshal_dump :to_h
alias :marshal_load :initialize

def add_ostruct_member!(key, name, initial)
@__table[key] = :"@#{name}"
instance_variable_set(name, initial)
class << self
attr_accessor key
end
end
private :add_ostruct_member!

def method_missing(mid, *args)
mid_str = mid.to_s
if mid_str[-1] == '='
if args.length != 1
raise ArgumentError,
"wrong number of arguments (#{args.length} for 1)",
caller(1)
end
add_ostruct_member!(mid_str[0, mid_str.size - 1].to_sym, args[0])
elsif args.length == 0 # and /\A[a-z_]\w*\z/ =~ mid #
nil
else
begin
super
rescue NoMethodError => err
err.backtrace.shift
raise
end
end
end

def [](name)
instance_key = @__table[name.to_sym]
instance_key && instance_variable_get(instance_key)
end

def []=(name, value)
name = name.to_sym
instance_key = @__table[name]
if instance_key
instance_variable_set(instance_key, value)
else
add_ostruct_member!(name, value)
end
end

def dig(name, *names)
result = self
while names.size != 0 && OpenStruct === result
begin
name = name.to_sym
rescue NoMethodError
raise TypeError, "#{name} is not a symbol nor a string"
end
instance_key = @__table[name]
result = instance_key && instance_variable_get(instance_key)
name = names.shift
end
result = result.dig(*names) if names.size != 0 && nil != result
result
end

def delete_field(name)
name = name.to_sym
instance_key = @__table.delete(key)
if instance_key
remove_instance_variable(instance_key)
class << self
remove_method(name, "#{name}=", instance_key, "#{instance_key}=")
end
else
raise NameError.new("no field `#{name}' in #{self}", name)
end
end

InspectKey = :__inspect_key__

def inspect
ids = (Thread.current[InspectKey] ||= [])
str = +'#<'
str << self.class
if ids.include?(object_id)
str << ' ...'
else
ids << object_id
comma = false
begin
@__table.each_pair do |key, instance_key|
value = instance_variable_get(instance_key)
str << ',' if comma
str << " #{key.inspect}=#{value.inspect}"
comma = true
end
ensure
ids.pop
end
end
str << '>'
str
end
alias :to_s :inspect

def ==(other)
return false unless other.kind_of?(OpenStruct)
return false unless @__table == other.instance_variable_get(:@__table)
@__table.each_value do |key|
unless instance_variable_get(key) == other.instance_variable_get(key)
return false
end
end
return true
end

def eql?(other)
return false unless other.kind_of?(OpenStruct)
return false unless @__table == other.instance_variable_get(:@__table)
@__table.each_value do |key|
unless instance_variable_get(key).eql? other.instance_variable_get(key)
return false
end
end
return true
end

def hash
acc = super ^ @__table.hash
@__table.each_value do |key|
acc ^= instance_variable_get(key).hash
end
acc
end
end
(2-2/2)