Class: Faster::OpenStruct

Inherits:
Object
  • Object
show all
Defined in:
lib/reflexive/faster_open_struct.rb

Overview

## Faster::OpenStruct

Up to 40 (!) times more memory efficient version of OpenStruct

Differences from Ruby MRI OpenStruct:

  1. Doesn’t ‘dup` passed initialization hash (NOTE: only reference to hash is stored)

  2. Doesn’t convert hash keys to symbols (by default string keys are used, with fallback to symbol keys)

  3. Creates methods on the fly on ‘OpenStruct` class, instead of singleton class. Uses `module_eval` with string to avoid holding scope references for every method.

  4. Refactored, crud clean, spec covered :)

Constant Summary collapse

InspectKey =

:nodoc:

:__inspect_key__

Instance Method Summary collapse

Constructor Details

#initialize(hash = nil) ⇒ OpenStruct

Returns a new instance of OpenStruct.



23
24
25
26
# File 'lib/reflexive/faster_open_struct.rb', line 23

def initialize(hash = nil)
  @hash = hash || {}
  @initialized_empty = hash == nil
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name_sym, *args) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/reflexive/faster_open_struct.rb', line 28

def method_missing(method_name_sym, *args)
  if method_name_sym.to_s[-1] == ?=
    if args.size != 1
      raise ArgumentError, "wrong number of arguments (#{args.size} for 1)", caller(1)
    end

    if self.frozen?
      raise TypeError, "can't modify frozen #{self.class}", caller(1)
    end

    __new_ostruct_member__(method_name_sym.to_s.chomp("="))
    send(method_name_sym, args[0])
  elsif args.size == 0
    __new_ostruct_member__(method_name_sym)
    send(method_name_sym)
  else
    raise NoMethodError, "undefined method `#{method_name_sym}' for #{self}", caller(1)
  end
end

Instance Method Details

#==(other) ⇒ Object

Compare this object and other for equality.



78
79
80
81
# File 'lib/reflexive/faster_open_struct.rb', line 78

def ==(other)
  return false unless other.is_a?(self.class)
  @hash == other.instance_variable_get(:@hash)
end

#__new_ostruct_member__(method_name_sym) ⇒ Object



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/reflexive/faster_open_struct.rb', line 48

def __new_ostruct_member__(method_name_sym)
  self.class.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
  def #{ method_name_sym }
    @hash.fetch("#{ method_name_sym }", @hash[:#{ method_name_sym }]) # read by default from string key, then try symbol
                                                                      # if string key doesn't exist
  end
  END_EVAL

  unless method_name_sym.to_s[-1] == ?? # can't define writer for predicate method
    self.class.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
    def #{ method_name_sym }=(val)
      if @hash.key?("#{ method_name_sym }") || @initialized_empty       # write by default to string key (when it is present
                                                                        # in initialization hash or initialization hash
                                                                        # wasn't provided)
        @hash["#{ method_name_sym }"] = val                             # if it doesn't exist - write to symbol key
      else
        @hash[:#{ method_name_sym }] = val
      end
    end
    END_EVAL
  end
end

#empty?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/reflexive/faster_open_struct.rb', line 71

def empty?
  @hash.empty?
end

#inspect_with_reentrant_guard(default = "...") ⇒ Object Also known as: inspect



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/reflexive/faster_open_struct.rb', line 94

def inspect_with_reentrant_guard(default = "...")
  Thread.current[InspectKey] ||= []

  if Thread.current[InspectKey].include?(self)
    return default # reenter detected
  end

  Thread.current[InspectKey] << self

  begin
    inspect_without_reentrant_guard
  ensure
    Thread.current[InspectKey].pop
  end
end