philiprehberger-dot_access

Tests Gem Version Last updated

Dot-notation accessor for nested hashes with nil-safe traversal

Requirements

  • Ruby >= 3.1

Installation

Add to your Gemfile:

gem "philiprehberger-dot_access"

Or install directly:

gem install philiprehberger-dot_access

Usage

require "philiprehberger/dot_access"

config = Philiprehberger::DotAccess.wrap({ database: { host: 'localhost', port: 5432 } })
config.database.host  # => "localhost"
config.database.port  # => 5432

Nil-Safe Traversal

config = Philiprehberger::DotAccess.wrap({ name: 'app' })
config.missing.nested.value  # => nil (never raises)

Path-Based Access

config = Philiprehberger::DotAccess.wrap({ a: { b: { c: 'deep' } } })
config.get('a.b.c')                        # => "deep"
config.get('a.b.missing', default: 'nope') # => "nope"

Immutable Updates

config  = Philiprehberger::DotAccess.wrap({ database: { port: 3306 } })
updated = config.set('database.port', 5432)

updated.database.port  # => 5432
config.database.port   # => 3306 (unchanged)

YAML Loading

config = Philiprehberger::DotAccess.from_yaml('config.yml')
config.database.host  # => value from YAML file

JSON Loading

config = Philiprehberger::DotAccess.from_json('{"database": {"host": "localhost"}}')
config.database.host  # => "localhost"

Path Existence Check

config = Philiprehberger::DotAccess.wrap({ a: { b: 1 }, x: nil })
config.exists?('a.b')     # => true
config.exists?('x')       # => true  (nil values still exist)
config.exists?('missing') # => false

Key Listing

config = Philiprehberger::DotAccess.wrap({ a: { b: { c: 1 } }, d: 2 })
config.keys              # => ["a.b.c", "d"]
config.keys(depth: 1)    # => ["a", "d"]

Strict Fetch

config = Philiprehberger::DotAccess.wrap({ database: { host: 'localhost' } })
config.fetch!('database.host')  # => "localhost"
config.fetch!('database.port')  # raises KeyError

Slice and Values At

config = Philiprehberger::DotAccess.wrap({ a: { b: 1, c: 2 }, d: 3 })
config.slice('a.b', 'd').to_h   # => { a: { b: 1 }, d: 3 }
config.values_at('a.b', 'd')    # => [1, 3]

Immutable Deletion

config  = Philiprehberger::DotAccess.wrap({ a: { b: 1, c: 2 }, d: 3 })
updated = config.delete('a.b')

updated.to_h  # => { a: { c: 2 }, d: 3 }
config.to_h   # => { a: { b: 1, c: 2 }, d: 3 } (unchanged)

Flatten

config = Philiprehberger::DotAccess.wrap({ a: { b: 1 }, c: 2 })
config.flatten  # => { "a.b" => 1, "c" => 2 }

Compact

config = Philiprehberger::DotAccess.wrap({ a: 1, b: nil, c: { d: nil, e: 2 }, f: [1, nil, 2] })
config.compact.to_h  # => { a: 1, c: { e: 2 }, f: [1, 2] }

Deep Merge

config = Philiprehberger::DotAccess.wrap({ a: { b: 1, c: 2 } })
merged = config.merge({ a: { c: 3, d: 4 } })

merged.to_h  # => { a: { b: 1, c: 3, d: 4 } }

Iteration

config = Philiprehberger::DotAccess.wrap({ a: 1, b: { c: 2 } })
config.each { |key, value| puts "#{key}: #{value}" }
config.map { |_key, value| value }   # Enumerable methods included
config.select { |key, _value| key == :a }

Size and Emptiness

config = Philiprehberger::DotAccess.wrap({ a: 1, b: 2 })
config.size    # => 2
config.empty?  # => false

Serialization

config = Philiprehberger::DotAccess.wrap({ database: { host: "localhost" } })
config.to_json  # => '{"database":{"host":"localhost"}}'
config.to_yaml  # => "---\ndatabase:\n  host: localhost\n"

Converting Back to Hash

config = Philiprehberger::DotAccess.wrap({ a: { b: 1 } })
config.to_h  # => { a: { b: 1 } }

API

Philiprehberger::DotAccess

Method Description
.wrap(hash) Wrap a hash for dot-notation access
.from_yaml(str_or_path) Parse YAML string or file and wrap the result
.from_json(str) Parse JSON string and wrap the result

Philiprehberger::DotAccess::Wrapper

Method Description
#get(path, default: nil) Retrieve a value by dot-separated path
#fetch!(path) Retrieve a value or raise KeyError if the path is missing
#set(path, value) Return a new wrapper with the value set at the path
#slice(*paths) Return a new wrapper containing only the given paths
#values_at(*paths) Return an array of values at the given paths
#exists?(path) Check if a dot-separated path exists (even if value is nil)
#keys(depth: nil) List all dot-path keys, optionally limited by depth
#delete(path) Return a new wrapper without the specified path
#flatten Convert to a flat hash with dot-path string keys
#merge(other) Deep merge with another Wrapper or Hash, returning a new Wrapper
#compact Return a new wrapper with all nil values removed at every depth
#each / #each_pair Iterate over top-level key-value pairs
#empty? Returns true if the wrapped hash has no keys
#size / #count Returns the number of top-level keys
#to_json Serialize back to a JSON string
#to_yaml Serialize back to a YAML string
#to_h Convert back to a plain hash

Philiprehberger::DotAccess::NullAccess

Method Description
#nil? Returns true
#<any_method> Returns nil for any method call

Development

bundle install
bundle exec rspec
bundle exec rubocop

Support

If you find this project useful:

Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT