Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- 2.1.2
# - rbx
- jruby
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ You can make Hashr raise an `IndexError` though like this:
config.foo? # => false
config.foo # => raises an IndexError "Key :foo is not defined."

Or you can set a custom error class like this:
class CustomError < StandardError; ;end

Hashr.raise_missing_keys = CustomError
config = Hashr.new
config.foo? # => false
config.foo # => raises a CustomError "Key :foo is not defined."

You can also anonymously overwrite core Hash methods like this:

config = Hashr.new(:count => 3) do
Expand Down
28 changes: 21 additions & 7 deletions lib/hashr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ class Hashr < Hash

TEMPLATE = new

class Keys
def initialize(hashr)
@hashr, @raise_missing = hashr, hashr.class.raise_missing_keys
end

def fetch!(name)
if !@hashr.key?(name) && @raise_missing
error_class = @raise_missing.is_a?(Class) ? @raise_missing : IndexError
raise(error_class.new("Key #{name.inspect} is not defined.").tap { |e| e.set_backtrace(caller)})
else
@hashr[name]
end
end

def respond_to?(method)
@raise_missing ? @hashr.key?(method) : true
end
end

class << self
attr_accessor :raise_missing_keys

Expand Down Expand Up @@ -59,11 +78,7 @@ def set(path, value, stack = [])
end

def respond_to?(method)
if self.class.raise_missing_keys
key?(method)
else
true
end
Keys.new(self).respond_to?(method)
end

def method_missing(name, *args, &block)
Expand All @@ -73,8 +88,7 @@ def method_missing(name, *args, &block)
when '='
self[name.to_s[0..-2].to_sym] = args.first
else
raise(IndexError.new("Key #{name.inspect} is not defined.")) if !key?(name) && self.class.raise_missing_keys
self[name]
Keys.new(self).fetch!(name)
end
end

Expand Down
5 changes: 5 additions & 0 deletions test/hashr_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ def teardown
assert_raises(IndexError) { Hashr.new(:foo => 'foo').bar }
end

test 'method access on a non-existing key raises a FooError when raise_missing_keys is FooError' do
Hashr.raise_missing_keys = FooError
assert_raises(FooError) { Hashr.new(:foo => 'foo').bar }
end

test 'method access on an existing nested key returns the value' do
assert_equal 'bar', Hashr.new(:foo => { :bar => 'bar' }).foo.bar
end
Expand Down
7 changes: 7 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@
require 'minitest/autorun'
require 'test_declarative'
require 'hashr'
require 'test_support'

module Minitest
class Test
include TestSupport
end
end
4 changes: 4 additions & 0 deletions test/test_support.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module TestSupport
class FooError < StandardError
end
end