Class: Philiprehberger::DotAccess::Wrapper

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/philiprehberger/dot_access.rb

Overview

Dot-notation wrapper for a hash

Constant Summary collapse

INDEX_SEGMENT =

Regex matching strings that should be treated as array indices (signed or unsigned integers).

/\A-?\d+\z/

Instance Method Summary collapse

Constructor Details

#initialize(hash) ⇒ Wrapper

Wrap the given Hash and freeze the resulting instance.

Parameters:

  • hash (Hash)

    the hash to wrap



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

def initialize(hash)
  @data = hash.each_with_object({}) do |(key, value), memo|
    memo[key.is_a?(String) ? key.to_sym : key] = value
  end
  freeze
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object (private)



369
370
371
372
373
374
375
376
377
# File 'lib/philiprehberger/dot_access.rb', line 369

def method_missing(name, *args)
  if @data.key?(name)
    wrap_value(@data[name])
  elsif name.to_s.end_with?('=') || !args.empty?
    super
  else
    NullAccess.new
  end
end

Instance Method Details

#==(other) ⇒ Boolean

Returns:

  • (Boolean)


361
362
363
364
365
# File 'lib/philiprehberger/dot_access.rb', line 361

def ==(other)
  return to_h == other.to_h if other.is_a?(Wrapper)

  false
end

#compactWrapper

Return a new Wrapper with all “nil“ values removed at every depth.

Returns:

  • (Wrapper)

    a new wrapper with nils removed from hashes and arrays



251
252
253
# File 'lib/philiprehberger/dot_access.rb', line 251

def compact
  Philiprehberger::DotAccess.wrap(deep_compact(to_h))
end

#delete(path) ⇒ Wrapper

Remove a key at a dot-path, returning a new Wrapper.

Integer-looking segments delete the matching array element when the current node is an Array.

Parameters:

  • path (String, Symbol)

    dot-separated key path

Returns:

  • (Wrapper)

    a new wrapper without the specified path



235
236
237
238
239
# File 'lib/philiprehberger/dot_access.rb', line 235

def delete(path)
  keys = path.to_s.split('.')
  new_data = deep_delete(to_h, keys)
  Wrapper.new(new_data)
end

#dig(key) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Dig into nested keys.

Parameters:

  • key (Symbol)

    the key to look up

Returns:

  • (Object)

    the (possibly wrapped) value



350
351
352
353
# File 'lib/philiprehberger/dot_access.rb', line 350

def dig(key)
  value = @data[key]
  wrap_value(value)
end

#each {|Symbol, Object| ... } ⇒ Enumerator Also known as: each_pair

Iterate over top-level key-value pairs.

Nested Hash values are yielded as wrapped Philiprehberger::DotAccess::Wrapper instances so block callers can chain dot-notation.

Yields:

  • (Symbol, Object)

    each key and its (possibly wrapped) value

Returns:

  • (Enumerator)

    if no block is given



289
290
291
292
293
294
295
# File 'lib/philiprehberger/dot_access.rb', line 289

def each(&)
  return enum_for(:each) unless block_given?

  @data.each do |key, value|
    yield key, wrap_value(value)
  end
end

#empty?Boolean

Returns “true“ if the wrapped hash has no keys.

Returns:

  • (Boolean)

    “true“ if the wrapped hash has no keys



300
301
302
# File 'lib/philiprehberger/dot_access.rb', line 300

def empty?
  @data.empty?
end

#exists?(path) ⇒ Boolean

Check whether a dot-separated path exists in the wrapped structure.

Parameters:

  • path (String, Symbol)

    dot-separated key path

Returns:

  • (Boolean)

    “true“ if the path exists (even when the value at the path is “nil“)



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/philiprehberger/dot_access.rb', line 163

def exists?(path)
  keys = path.to_s.split('.')
  current = @data
  keys.each do |key|
    case current
    when Hash
      return false unless current.key?(key.to_sym)

      current = current[key.to_sym]
    when Array
      index = array_index(key, current)
      return false if index.nil?

      current = current[index]
    when Wrapper
      return false unless current.key?(key.to_sym)

      current = current[key.to_sym]
    else
      return false
    end
  end
  true
end

#fetch!(path) ⇒ Object

Fetch a value at a dot-path, raising if missing.

Parameters:

  • path (String, Symbol)

    dot-separated key path

Returns:

  • (Object)

    the value at the path

Raises:

  • (KeyError)

    if the path does not exist



201
202
203
204
205
# File 'lib/philiprehberger/dot_access.rb', line 201

def fetch!(path)
  raise KeyError, "path not found: #{path.inspect}" unless exists?(path)

  get(path)
end

#flattenHash

Flatten the nested structure into a hash whose keys are dot-paths.

Returns:

  • (Hash)

    flat hash where keys are dot-path strings



244
245
246
# File 'lib/philiprehberger/dot_access.rb', line 244

def flatten
  flatten_hash(@data, '')
end

#get(path, default: nil) ⇒ Object

Access a value by dot-path string.

Path segments that look like integers (e.g. “0“, “-1“) are interpreted as array indices when the value currently being traversed is an Array. Otherwise they are treated as Hash keys.

Parameters:

  • path (String, Symbol)

    dot-separated key path

  • default (Object) (defaults to: nil)

    value returned if the path is not found

Returns:

  • (Object)

    the value at the path, or “default“



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/philiprehberger/dot_access.rb', line 123

def get(path, default: nil)
  keys = path.to_s.split('.')
  result = keys.reduce(@data) do |current, key|
    case current
    when Hash then current[key.to_sym]
    when Array
      index = array_index(key, current)
      return default if index.nil?

      current[index]
    when Wrapper then current[key.to_sym]
    else return default
    end
  end

  result.nil? ? default : result
end

#inspectString

Returns:

  • (String)


356
357
358
# File 'lib/philiprehberger/dot_access.rb', line 356

def inspect
  "#<DotAccess::Wrapper #{to_h.inspect}>"
end

#key?(key) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Check if a key exists in the underlying data.

Parameters:

  • key (Symbol)

    the key to check

Returns:

  • (Boolean)


332
333
334
# File 'lib/philiprehberger/dot_access.rb', line 332

def key?(key)
  @data.key?(key)
end

#keys(depth: nil) ⇒ Array<String>

List every dot-path in the wrapped structure.

Parameters:

  • depth (Integer, nil) (defaults to: nil)

    maximum traversal depth (“nil“ for unlimited)

Returns:

  • (Array<String>)

    dot-path strings



192
193
194
# File 'lib/philiprehberger/dot_access.rb', line 192

def keys(depth: nil)
  collect_keys(@data, '', depth, 1)
end

#merge(other) ⇒ Wrapper

Deep merge with another Wrapper or Hash.

Parameters:

  • other (Wrapper, Hash)

    the other structure to merge

Returns:

  • (Wrapper)

    a new wrapper with merged values



259
260
261
262
263
# File 'lib/philiprehberger/dot_access.rb', line 259

def merge(other)
  other_hash = other.is_a?(Wrapper) ? other.to_h : symbolize_keys(other)
  merged = deep_merge(to_h, other_hash)
  Wrapper.new(merged)
end

#set(path, value) ⇒ Wrapper

Set a value at a dot-path, returning a new Wrapper.

Integer-looking path segments are applied as array indices when the current node is an Array. Out-of-bounds indices raise ArgumentError; negative indices follow Ruby conventions.

Parameters:

  • path (String, Symbol)

    dot-separated key path

  • value (Object)

    the value to set

Returns:

  • (Wrapper)

    a new wrapper with the updated value

Raises:

  • (ArgumentError)

    when an integer segment is out of bounds for the current Array node



152
153
154
155
156
# File 'lib/philiprehberger/dot_access.rb', line 152

def set(path, value)
  keys = path.to_s.split('.')
  new_data = deep_set(to_h, keys, value)
  Wrapper.new(new_data)
end

#sizeInteger Also known as: count

Returns the number of top-level keys.

Returns:

  • (Integer)

    the number of top-level keys



305
306
307
# File 'lib/philiprehberger/dot_access.rb', line 305

def size
  @data.size
end

#slice(*paths) ⇒ Wrapper

Return a new Wrapper containing only the specified dot-paths.

Parameters:

  • paths (Array<String, Symbol>)

    dot-separated key paths to retain

Returns:

  • (Wrapper)

    a new wrapper with only the given paths



211
212
213
214
215
216
217
218
# File 'lib/philiprehberger/dot_access.rb', line 211

def slice(*paths)
  new_data = paths.reduce({}) do |acc, path|
    next acc unless exists?(path)

    deep_set(acc, path.to_s.split('.'), get(path))
  end
  Wrapper.new(new_data)
end

#to_hHash

Return the underlying hash with symbol keys.

Returns:

  • (Hash)

    the original hash structure



339
340
341
342
343
# File 'lib/philiprehberger/dot_access.rb', line 339

def to_h
  @data.each_with_object({}) do |(key, value), memo|
    memo[key] = value.is_a?(Wrapper) ? value.to_h : value
  end
end

#to_json(*args) ⇒ String

Serialize the wrapped hash to a JSON string.

Parameters:

  • args (Array<Object>)

    passed through to “Hash#to_json“

Returns:

  • (String)

    JSON representation



315
316
317
# File 'lib/philiprehberger/dot_access.rb', line 315

def to_json(*args)
  to_h.to_json(*args)
end

#to_yaml(*args) ⇒ String

Serialize the wrapped hash to a YAML string.

Parameters:

  • args (Array<Object>)

    passed through to “Hash#to_yaml“

Returns:

  • (String)

    YAML representation



323
324
325
# File 'lib/philiprehberger/dot_access.rb', line 323

def to_yaml(*args)
  to_h.to_yaml(*args)
end

#update(paths_hash) ⇒ Wrapper

Batch-set multiple dot-paths, returning a new Wrapper.

Applies every entry in “paths_hash“ in iteration order, following the same semantics as #set. The receiver is not mutated.

Parameters:

  • paths_hash (Hash{String, Symbol => Object})

    map of dot-paths to values

Returns:

  • (Wrapper)

    a new frozen wrapper with every path applied

Raises:

  • (ArgumentError)

    when an integer segment is out of bounds for an Array node (propagated from #set)



275
276
277
278
279
280
# File 'lib/philiprehberger/dot_access.rb', line 275

def update(paths_hash)
  new_data = paths_hash.reduce(to_h) do |acc, (path, value)|
    deep_set(acc, path.to_s.split('.'), value)
  end
  Wrapper.new(new_data)
end

#values_at(*paths) ⇒ Array<Object>

Return values at the given dot-paths as an array.

Parameters:

  • paths (Array<String, Symbol>)

    dot-separated key paths

Returns:

  • (Array<Object>)

    values in the order of the given paths



224
225
226
# File 'lib/philiprehberger/dot_access.rb', line 224

def values_at(*paths)
  paths.map { |path| get(path) }
end