Class: Inquery::MethodAccessibleHash

Inherits:
Object
  • Object
show all
Defined in:
lib/inquery/method_accessible_hash.rb

Overview

A safe alternative for ‘OpenStruct` in Ruby. It provides convenient method access to a set of key-value pairs, just like `OpenStruct`, but uses `method_missing` instead of defining methods on-the-fly.

Unlike a ‘Hash`, this class deliberately does not inherit from `Hash` (or include `Enumerable`). If it did, keys whose names collide with `Hash` / `Enumerable` methods (e.g. `group_by`, `count`, `zip`, `select`, …) would invoke the inherited method instead of returning the stored value, because `method_missing` is only called for methods that are not already defined.

Usage example:

“‘ruby default_options = { foo: :bar } options = MethodAccessibleHash.new(default_options) options = :green options.foo # => :bar options.color # => :green “`

Constant Summary collapse

CONVERSION_METHODS =

Methods that participate in Ruby’s implicit type-coercion protocol. We must neither pretend to respond to these (‘respond_to_missing?`) nor fabricate a `nil` value for them (`method_missing`). Otherwise constructs such as `**hash`, `Hash(obj)`, `Array(obj)` or Rails’ implicit conversions would detect the method, call it, receive ‘nil` and raise a `TypeError`. Keys actually present under one of these names are still returned normally.

%i[to_ary to_a to_hash to_str to_int to_proc].freeze

Instance Method Summary collapse

Constructor Details

#initialize(hash = {}) ⇒ MethodAccessibleHash

Takes an optional hash as argument and constructs a new MethodAccessibleHash. Keys are symbolized.



33
34
35
36
37
38
# File 'lib/inquery/method_accessible_hash.rb', line 33

def initialize(hash = {})
  @table = {}
  (hash || {}).each do |key, value|
    @table[key.to_sym] = value
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args, &_block) ⇒ Object



89
90
91
92
93
94
95
96
97
98
# File 'lib/inquery/method_accessible_hash.rb', line 89

def method_missing(method, *args, &_block)
  name = method.to_s
  if name.end_with?('=')
    self[name[0..-2].to_sym] = args.first
  elsif CONVERSION_METHODS.include?(method) && !@table.key?(method)
    super
  else
    @table[method.to_sym]
  end
end

Instance Method Details

#==(other) ⇒ Object



65
66
67
68
69
70
71
72
73
74
# File 'lib/inquery/method_accessible_hash.rb', line 65

def ==(other)
  case other
  when MethodAccessibleHash
    to_h == other.to_h
  when ::Hash
    to_h == other.symbolize_keys
  else
    false
  end
end

#[](key) ⇒ Object

Returns the value stored under the given key. Keys are accessed indifferently as the storage is symbolized.



42
43
44
# File 'lib/inquery/method_accessible_hash.rb', line 42

def [](key)
  @table[key.to_sym]
end

#[]=(key, value) ⇒ Object

Stores the given value under the given (symbolized) key. Modifying a frozen instance raises a ‘FrozenError`, because `@table` is frozen alongside the instance (see `freeze` and `initialize_copy`).



49
50
51
# File 'lib/inquery/method_accessible_hash.rb', line 49

def []=(key, value)
  @table[key.to_sym] = value
end

#freezeObject



83
84
85
86
# File 'lib/inquery/method_accessible_hash.rb', line 83

def freeze
  @table.freeze
  super
end

#merge(other = {}) ⇒ Object

Returns a new MethodAccessibleHash with the given hash merged in.



60
61
62
# File 'lib/inquery/method_accessible_hash.rb', line 60

def merge(other = {})
  self.class.new(to_h.merge(other.to_h.symbolize_keys))
end

#respond_to_missing?(method, _include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


101
102
103
104
105
# File 'lib/inquery/method_accessible_hash.rb', line 101

def respond_to_missing?(method, _include_private = false)
  return false if CONVERSION_METHODS.include?(method) && !@table.key?(method)

  true
end

#to_hObject

Returns a (shallow) copy of the underlying data as a plain Hash with symbol keys.



55
56
57
# File 'lib/inquery/method_accessible_hash.rb', line 55

def to_h
  @table.dup
end

#to_sObject Also known as: inspect



77
78
79
# File 'lib/inquery/method_accessible_hash.rb', line 77

def to_s
  @table.to_s
end