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
This commit is contained in:
parent
f0e5eac199
commit
61b76fab81
|
@ -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).
|
||||
|
|
19
README.md
19
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.
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue