summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Boling <peter.boling@gmail.com>2015-10-23 09:53:42 -0700
committerPeter Boling <peter.boling@gmail.com>2015-10-23 09:53:42 -0700
commit61b76fab81f9b876a3bdd7239b8509d2b69dd625 (patch)
tree80ef78e8fad669eb652ba3c8026fbdaa3189eae7
parentf0e5eac1997946975c447b388889dd9675b41ce9 (diff)
downloadhashie-61b76fab81f9b876a3bdd7239b8509d2b69dd625.tar.gz
SRP: The StrictKeyAccess extension will raise an error whenever a key is accessed that does not exist in the hash.
In Python a "Hash" is called a "Dictionary", and ... > "It is an error to extract a value using a non-existent key." See: https://docs.python.org/2/tutorial/datastructures.html#dictionaries EXAMPLE: class StrictHash < Hash include Hashie::Extensions::StrictKeyAccess end >> hash = StrictHash[foo: "bar"] => {:foo=>"bar"} >> hash[:foo] => "bar" >> hash[:cow] KeyError: key not found: :cow
-rw-r--r--CHANGELOG.md1
-rw-r--r--README.md19
-rw-r--r--lib/hashie.rb1
-rw-r--r--lib/hashie/extensions/strict_key_access.rb71
-rw-r--r--spec/hashie/extensions/strict_key_access_spec.rb99
5 files changed, 191 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57323f9..b758559 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
## Next Release
* Your contribution here.
+* [#314](https://github.com/intridea/hashie/pull/314): Added a `StrictKeyAccess` extension that will raise an error whenever a key is accessed that does not exist in the hash - [@pboling](https://github.com/pboling).
* [#304](https://github.com/intridea/hashie/pull/304): Ensured compatibility of `Hash` extensions with singleton objects - [@regexident](https://github.com/regexident).
* [#306](https://github.com/intridea/hashie/pull/306): Added `Hashie::Extensions::Dash::Coercion` - [@marshall-lee](https://github.com/marshall-lee).
* [#310](https://github.com/intridea/hashie/pull/310): Fixed `Hashie::Extensions::SafeAssignment` bug with private methods - [@marshall-lee](https://github.com/marshall-lee).
diff --git a/README.md b/README.md
index a2f381c..eba8bcc 100644
--- a/README.md
+++ b/README.md
@@ -392,6 +392,25 @@ books.deep_locate -> (key, value, object) { key == :pages && value <= 120 }
# => [{:title=>"Ruby for beginners", :pages=>120}, {:title=>"CSS for intermediates", :pages=>80}]
```
+## StrictKeyAccess
+
+This extension can be mixed in to allow a Hash to raise an error when attempting to extract a value using a non-existent key.
+
+### Example:
+
+```ruby
+class StrictKeyAccessHash < Hash
+ include Hashie::Extensions::StrictKeyAccess
+end
+
+>> hash = StrictKeyAccessHash[foo: "bar"]
+=> {:foo=>"bar"}
+>> hash[:foo]
+=> "bar"
+>> hash[:cow]
+ KeyError: key not found: :cow
+```
+
## Mash
Mash is an extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended. It is intended to give the user easier access to the objects within the Mash through a property-like syntax, while still retaining all Hash functionality.
diff --git a/lib/hashie.rb b/lib/hashie.rb
index b0f771c..59a7c25 100644
--- a/lib/hashie.rb
+++ b/lib/hashie.rb
@@ -26,6 +26,7 @@ module Hashie
autoload :PrettyInspect, 'hashie/extensions/pretty_inspect'
autoload :KeyConversion, 'hashie/extensions/key_conversion'
autoload :MethodAccessWithOverride, 'hashie/extensions/method_access'
+ autoload :StrictKeyAccess, 'hashie/extensions/strict_key_access'
module Parsers
autoload :YamlErbParser, 'hashie/extensions/parsers/yaml_erb_parser'
diff --git a/lib/hashie/extensions/strict_key_access.rb b/lib/hashie/extensions/strict_key_access.rb
new file mode 100644
index 0000000..6972252
--- /dev/null
+++ b/lib/hashie/extensions/strict_key_access.rb
@@ -0,0 +1,71 @@
+module Hashie
+ module Extensions
+ # SRP: This extension will fail an error whenever a key is accessed that does not exist in the hash.
+ #
+ # EXAMPLE:
+ #
+ # class StrictKeyAccessHash < Hash
+ # include Hashie::Extensions::StrictKeyAccess
+ # end
+ #
+ # >> hash = StrictKeyAccessHash[foo: "bar"]
+ # => {:foo=>"bar"}
+ # >> hash[:foo]
+ # => "bar"
+ # >> hash[:cow]
+ # KeyError: key not found: :cow
+ #
+ # NOTE: For googlers coming from Python to Ruby, this extension makes a Hash behave like a "Dictionary".
+ #
+ module StrictKeyAccess
+ class DefaultError < StandardError
+ def initialize(msg = 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense', *args)
+ super
+ end
+ end
+
+ # NOTE: This extension would break the default behavior of Hash initialization:
+ #
+ # >> a = StrictKeyAccessHash.new(a: :b)
+ # => {}
+ # >> a[:a]
+ # KeyError: key not found: :a
+ #
+ # Includes the Hashie::Extensions::MergeInitializer extension to get around that problem.
+ # Also note that defaults still don't make any sense with a StrictKeyAccess.
+ def self.included(base)
+ # Can only include into classes with a hash initializer
+ base.send(:include, Hashie::Extensions::MergeInitializer)
+ end
+
+ def [](key)
+ fetch(key)
+ end
+
+ def default(_ = nil)
+ fail DefaultError
+ end
+
+ def default=(_)
+ fail DefaultError
+ end
+
+ def default_proc
+ fail DefaultError
+ end
+
+ def default_proc=(_)
+ fail DefaultError
+ end
+
+ def key(value)
+ result = super
+ if result.nil? && (!key?(result) || self[result] != value)
+ fail KeyError, "key not found with value of #{value.inspect}"
+ else
+ result
+ end
+ end
+ end
+ end
+end
diff --git a/spec/hashie/extensions/strict_key_access_spec.rb b/spec/hashie/extensions/strict_key_access_spec.rb
new file mode 100644
index 0000000..2e47064
--- /dev/null
+++ b/spec/hashie/extensions/strict_key_access_spec.rb
@@ -0,0 +1,99 @@
+require 'spec_helper'
+
+describe Hashie::Extensions::StrictKeyAccess do
+ class StrictKeyAccessHash < Hash
+ include Hashie::Extensions::StrictKeyAccess
+ end
+
+ shared_examples_for 'StrictKeyAccess with valid key' do |options = {}|
+ before { pending_for(options[:pending]) } if options[:pending]
+ context 'access' do
+ it('returns value') do
+ expect(instance[valid_key]).to eq valid_value
+ end
+ end
+ context 'lookup' do
+ it('returns key') do
+ expect(instance.key(valid_value)).to eq valid_key
+ end
+ end
+ end
+ shared_examples_for 'StrictKeyAccess with invalid key' do |options = {}|
+ before { pending_for(options[:pending]) } if options[:pending]
+ context 'access' do
+ it('raises an error') do
+ # Formatting of the error message varies on Rubinius and ruby-head
+ expect { instance[invalid_key] }.to raise_error KeyError
+ end
+ end
+ context 'lookup' do
+ it('raises an error') do
+ # Formatting of the error message does not vary here because raised by StrictKeyAccess
+ expect { instance.key(invalid_value) }.to raise_error KeyError,
+ %(key not found with value of #{invalid_value.inspect})
+ end
+ end
+ end
+ shared_examples_for 'StrictKeyAccess with exploding defaults' do
+ context '#default' do
+ it 'raises an error' do
+ expect { instance.default(invalid_key) }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError,
+ 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
+ end
+ end
+ context '#default=' do
+ it 'raises an error' do
+ expect { instance.default = invalid_key }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError,
+ 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
+ end
+ end
+ context '#default_proc' do
+ it 'raises an error' do
+ expect { instance.default_proc }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError,
+ 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
+ end
+ end
+ context '#default_proc=' do
+ it 'raises an error' do
+ expect { instance.default_proc = proc {} }.to raise_error Hashie::Extensions::StrictKeyAccess::DefaultError,
+ 'Setting or using a default with Hashie::Extensions::StrictKeyAccess does not make sense'
+ end
+ end
+ end
+
+ let(:klass) { StrictKeyAccessHash }
+ let(:instance) { StrictKeyAccessHash.new(*args) }
+ let(:args) do
+ [
+ { valid_key => valid_value }
+ ]
+ end
+ let(:valid_key) { :abc }
+ let(:valid_value) { 'def' }
+ let(:invalid_key) { :mega }
+ let(:invalid_value) { 'death' }
+
+ context '.new' do
+ it_behaves_like 'StrictKeyAccess with valid key'
+ it_behaves_like 'StrictKeyAccess with invalid key'
+ it_behaves_like 'StrictKeyAccess with exploding defaults'
+ end
+
+ context '[]' do
+ let(:instance) { StrictKeyAccessHash[*args] }
+ it_behaves_like 'StrictKeyAccess with valid key', pending: { engine: 'rbx' }
+ it_behaves_like 'StrictKeyAccess with invalid key', pending: { engine: 'rbx' }
+ it_behaves_like 'StrictKeyAccess with exploding defaults'
+ end
+
+ context 'with default' do
+ let(:args) do
+ [
+ { valid_key => valid_value }, invalid_value
+ ]
+ end
+ it_behaves_like 'StrictKeyAccess with valid key'
+ it_behaves_like 'StrictKeyAccess with invalid key'
+ it_behaves_like 'StrictKeyAccess with exploding defaults'
+ end
+end