Fix except use in Mash#load (#508)

This commit is contained in:
Bobby McDonald 2020-01-14 08:50:43 -05:00 committed by Daniel Doubrovkine (dB.) @dblockdotorg
parent d2071d82a2
commit 25fe2f747e
12 changed files with 403 additions and 283 deletions

View File

@ -28,6 +28,7 @@ matrix:
dist: bionic
- rvm: rbx-3
dist: trusty
bundler_args: --retry 0
- rvm: jruby-9.0.5.0
dist: trusty
- rvm: jruby-head

View File

@ -32,6 +32,8 @@ scheme are considered to be bugs.
* [#467](https://github.com/intridea/hashie/pull/467): Fixed `DeepMerge#deep_merge` mutating nested values within the receiver - [@michaelherold](https://github.com/michaelherold).
* [#505](https://github.com/hashie/hashie/pull/505): Ensure that `Hashie::Array`s are not deconverted within `Hashie::Mash`es to make `Mash#dig` work properly - [@michaelherold](https://github.com/michaelherold).
* [#507](https://github.com/hashie/hashie/pull/507): Suppress `Psych.safe_load` arg warn when using Psych 3.1.0+ - [@koic](https://github.com/koic).
* [#508](https://github.com/hashie/hashie/pull/508): Fixed `Mash.load` no longer uses Rails-only `#except` - [@bobbymcwho](https://github.com/bobbymcwho).
* [#508](https://github.com/hashie/hashie/pull/508): Fixed `Hashie::Extensions::DeepMerge` `#deep_merge` not correctly dup'ing sub-hashes if active_support hash extensions were not present - [@bobbymcwho](https://github.com/bobbymcwho).
* [#510](https://github.com/hashie/hashie/pull/510): Ensure that `Hashie::Mash#compact` is only defined on Ruby version >= 2.4.0 - [@bobbymcwho](https://github.com/bobbymcwho).
* Your contribution here.

View File

@ -38,6 +38,8 @@ module Hashie
hash[k] =
if hash.key?(k) && hash[k].is_a?(::Hash) && v.is_a?(::Hash)
_recursive_merge(hash[k], v, &block)
elsif v.is_a?(::Hash)
_recursive_merge({}, v, &block)
elsif hash.key?(k) && block_given?
yield(k, hash[k], v)
else

View File

@ -74,8 +74,9 @@ module Hashie
return @_mashes[path] if @_mashes.key?(path)
raise ArgumentError, "The following file doesn't exist: #{path}" unless File.file?(path)
parser = options.fetch(:parser) { Hashie::Extensions::Parsers::YamlErbParser }
@_mashes[path] = new(parser.perform(path, options.except(:parser))).freeze
options = options.dup
parser = options.delete(:parser) { Hashie::Extensions::Parsers::YamlErbParser }
@_mashes[path] = new(parser.perform(path, options)).freeze
end
def to_module(mash_method_name = :settings)

View File

@ -56,9 +56,9 @@ class DeferredWithSelfTest < Hashie::Dash
end
describe DashTestDefaultProc do
it 'as_json behaves correctly with default proc' do
it 'to_json behaves correctly with default proc' do
object = described_class.new
expect(object.as_json).to be == { 'fields' => [] }
expect(object.to_json).to be == '{"fields":[]}'
end
end

View File

@ -1,5 +1,4 @@
require 'spec_helper'
require 'active_support/core_ext/hash/indifferent_access'
describe Hashie::Extensions::DeepFind do
subject { Class.new(Hash) { include Hashie::Extensions::DeepFind } }
@ -71,41 +70,6 @@ describe Hashie::Extensions::DeepFind do
end
end
context 'on an ActiveSupport::HashWithIndifferentAccess' do
subject(:instance) { hash.with_indifferent_access.extend(Hashie::Extensions::DeepFind) }
describe '#deep_find' do
it 'indifferently detects a value from a nested hash' do
expect(instance.deep_find(:address)).to eq('123 Library St.')
expect(instance.deep_find('address')).to eq('123 Library St.')
end
it 'indifferently detects a value from a nested array' do
expect(instance.deep_find(:title)).to eq('Call of the Wild')
expect(instance.deep_find('title')).to eq('Call of the Wild')
end
it 'indifferently returns nil if it does not find a match' do
expect(instance.deep_find(:wahoo)).to be_nil
expect(instance.deep_find('wahoo')).to be_nil
end
end
describe '#deep_find_all' do
it 'indifferently detects all values from a nested hash' do
expect(instance.deep_find_all(:title))
.to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
expect(instance.deep_find_all('title'))
.to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
end
it 'indifferently returns nil if it does not find any matches' do
expect(instance.deep_find_all(:wahoo)).to be_nil
expect(instance.deep_find_all('wahoo')).to be_nil
end
end
end
context 'on a Hash including Hashie::Extensions::IndifferentAccess' do
let(:klass) { Class.new(Hash) { include Hashie::Extensions::IndifferentAccess } }
subject(:instance) { klass[hash.dup].extend(Hashie::Extensions::DeepFind) }

View File

@ -1,5 +1,4 @@
require 'spec_helper'
require 'active_support/core_ext/hash/indifferent_access'
describe Hashie::Extensions::DeepLocate do
let(:hash) do
@ -123,16 +122,4 @@ describe Hashie::Extensions::DeepLocate do
expect(instance.deep_locate(:bool)).to eq([hash[:query]])
end
end
context 'on an ActiveSupport::HashWithIndifferentAccess' do
let(:instance) { hash.dup.with_indifferent_access }
it 'can locate symbolic keys' do
expect(described_class.deep_locate(:lsr10, instance)).to eq ['lsr10' => { 'gte' => 2014 }]
end
it 'can locate string keys' do
expect(described_class.deep_locate('lsr10', instance)).to eq ['lsr10' => { 'gte' => 2014 }]
end
end
end

View File

@ -1,208 +0,0 @@
# This set of tests verifies that Hashie::Extensions::IndifferentAccess works with
# ActiveSupport HashWithIndifferentAccess hashes. See #164 and #166 for details.
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/hash'
require 'spec_helper'
describe Hashie::Extensions::IndifferentAccess do
class IndifferentHashWithMergeInitializer < Hash
include Hashie::Extensions::MergeInitializer
include Hashie::Extensions::IndifferentAccess
class << self
alias build new
end
end
class IndifferentHashWithArrayInitializer < Hash
include Hashie::Extensions::IndifferentAccess
class << self
alias build []
end
end
class IndifferentHashWithTryConvertInitializer < Hash
include Hashie::Extensions::IndifferentAccess
class << self
alias build try_convert
end
end
class CoercableHash < Hash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
end
class MashWithIndifferentAccess < Hashie::Mash
include Hashie::Extensions::IndifferentAccess
end
shared_examples_for 'hash with indifferent access' do
it 'is able to access via string or symbol' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(abc: 123)
h = subject.build(indifferent_hash)
expect(h[:abc]).to eq 123
expect(h['abc']).to eq 123
end
describe '#values_at' do
it 'indifferently finds values' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(
:foo => 'bar', 'baz' => 'qux'
)
h = subject.build(indifferent_hash)
expect(h.values_at('foo', :baz)).to eq %w[bar qux]
end
end
describe '#fetch' do
it 'works like normal fetch, but indifferent' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
h = subject.build(indifferent_hash)
expect(h.fetch(:foo)).to eq h.fetch('foo')
expect(h.fetch(:foo)).to eq 'bar'
end
end
describe '#delete' do
it 'deletes indifferently' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(
:foo => 'bar',
'baz' => 'qux'
)
h = subject.build(indifferent_hash)
h.delete('foo')
h.delete(:baz)
expect(h).to be_empty
end
end
describe '#key?' do
let(:h) do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
subject.build(indifferent_hash)
end
it 'finds it indifferently' do
expect(h).to be_key(:foo)
expect(h).to be_key('foo')
end
%w[include? member? has_key?].each do |key_alias|
it "is aliased as #{key_alias}" do
expect(h.send(key_alias.to_sym, :foo)).to be(true)
expect(h.send(key_alias.to_sym, 'foo')).to be(true)
end
end
end
describe '#update' do
let(:h) do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
subject.build(indifferent_hash)
end
it 'allows keys to be indifferent still' do
h.update(baz: 'qux')
expect(h['foo']).to eq 'bar'
expect(h['baz']).to eq 'qux'
end
it 'recursively injects indifference into sub-hashes' do
h.update(baz: { qux: 'abc' })
expect(h['baz']['qux']).to eq 'abc'
end
it 'does not change the ancestors of the injected object class' do
h.update(baz: { qux: 'abc' })
expect({}).not_to be_respond_to(:indifferent_access?)
end
end
describe '#replace' do
let(:h) do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
subject.build(indifferent_hash).replace(bar: 'baz', hi: 'bye')
end
it 'returns self' do
expect(h).to be_a(subject)
end
it 'removes old keys' do
[:foo, 'foo'].each do |k|
expect(h[k]).to be_nil
expect(h.key?(k)).to be_falsy
end
end
it 'creates new keys with indifferent access' do
[:bar, 'bar', :hi, 'hi'].each { |k| expect(h.key?(k)).to be_truthy }
expect(h[:bar]).to eq 'baz'
expect(h['bar']).to eq 'baz'
expect(h[:hi]).to eq 'bye'
expect(h['hi']).to eq 'bye'
end
end
describe '#try_convert' do
describe 'with conversion' do
let(:h) do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
subject.try_convert(indifferent_hash)
end
it 'is a subject' do
expect(h).to be_a(subject)
end
end
describe 'without conversion' do
let(:h) { subject.try_convert('{ :foo => bar }') }
it 'is nil' do
expect(h).to be_nil
end
end
end
end
describe 'with merge initializer' do
subject { IndifferentHashWithMergeInitializer }
it_should_behave_like 'hash with indifferent access'
end
describe 'with array initializer' do
subject { IndifferentHashWithArrayInitializer }
it_should_behave_like 'hash with indifferent access'
end
describe 'with try convert initializer' do
subject { IndifferentHashWithTryConvertInitializer }
it_should_behave_like 'hash with indifferent access'
end
describe 'with coercion' do
subject { CoercableHash }
let(:instance) { subject.new }
it 'supports coercion for ActiveSupport::HashWithIndifferentAccess' do
subject.coerce_key :foo, ActiveSupport::HashWithIndifferentAccess.new(Coercable => Coercable)
instance[:foo] = { 'bar_key' => 'bar_value', 'bar2_key' => 'bar2_value' }
expect(instance[:foo].keys).to all(be_coerced)
expect(instance[:foo].values).to all(be_coerced)
expect(instance[:foo]).to be_a(ActiveSupport::HashWithIndifferentAccess)
end
end
describe 'Mash with indifferent access' do
it 'is able to be created for a deep nested HashWithIndifferentAccess' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(abc: { def: 123 })
MashWithIndifferentAccess.new(indifferent_hash)
end
end
end

View File

@ -147,7 +147,7 @@ describe Hashie::Mash do
mash[:test_key] = 'Test value'
expect { mash[:test_key] = 'A new value' }.not_to raise_error
expect(logger_output).to be_blank
expect(logger_output).to be_empty
end
it 'does not write to the logger when warnings are disabled' do
@ -156,7 +156,7 @@ describe Hashie::Mash do
end
mash_class.new('trust' => { 'two' => 2 })
expect(logger_output).to be_blank
expect(logger_output).to be_empty
end
it 'cannot disable logging on the base Mash' do
@ -173,7 +173,7 @@ describe Hashie::Mash do
grandchild_class.new('trust' => { 'two' => 2 })
expect(logger_output).to be_blank
expect(logger_output).to be_empty
end
it 'writes to logger when a key is overridden that is not ignored' do
@ -182,7 +182,7 @@ describe Hashie::Mash do
end
mash_class.new('address' => { 'zip' => '90210' })
expect(logger_output).not_to be_blank
expect(logger_output).not_to be_empty
end
it 'does not write to logger when a key is overridden that is ignored' do
@ -191,7 +191,7 @@ describe Hashie::Mash do
end
mash_class.new('address' => { 'zip' => '90210' })
expect(logger_output).to be_blank
expect(logger_output).to be_empty
end
it 'carries over the ignored warnings list for warnings on grandchild classes' do
@ -203,7 +203,7 @@ describe Hashie::Mash do
grandchild_class.new('address' => { 'zip' => '90210' }, 'merge' => true)
expect(grandchild_class.disabled_warnings).to eq(%i[zip merge])
expect(logger_output).to be_blank
expect(logger_output).to be_empty
end
context 'multiple disable_warnings calls' do
@ -229,7 +229,7 @@ describe Hashie::Mash do
child_class.new('address' => { 'zip' => '90210' }, 'merge' => true, 'cycle' => 'bi')
expect(child_class.disabled_warnings).to eq([])
expect(logger_output).to be_blank
expect(logger_output).to be_empty
end
end
@ -848,18 +848,6 @@ describe Hashie::Mash do
end
end
describe '#extractable_options?' do
require 'active_support'
subject { described_class.new(name: 'foo') }
let(:args) { [101, 'bar', subject] }
it 'can be extracted from an array' do
expect(args.extract_options!).to eq subject
expect(args).to eq [101, 'bar']
end
end
describe '#reverse_merge' do
subject { described_class.new(a: 1, b: 2) }

View File

@ -0,0 +1,15 @@
source 'http://rubygems.org'
gem 'hashie', path: '../../..'
require File.expand_path('../../../../lib/hashie/extensions/ruby_version', __FILE__)
# rubocop:disable Bundler/DuplicatedGem
if Hashie::Extensions::RubyVersion.new(RUBY_VERSION) >= Hashie::Extensions::RubyVersion.new('2.4.0')
gem 'activesupport', '~> 5.x', require: false
else
gem 'activesupport', '~> 4.x', require: false
end
# rubocop:enable Bundler/DuplicatedGem
gem 'rake'
gem 'rspec', '~> 3.5.0'

View File

@ -0,0 +1,371 @@
require 'active_support'
require 'active_support/core_ext'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/hash'
require 'hashie'
RSpec.configure do |config|
config.expect_with :rspec do |expect|
expect.syntax = :expect
end
end
RSpec.describe Hashie::Mash do
describe '#extractable_options?' do
subject { Hashie::Mash.new(name: 'foo') }
let(:args) { [101, 'bar', subject] }
it 'can be extracted from an array' do
expect(args.extract_options!).to eq subject
expect(args).to eq [101, 'bar']
end
end
end
RSpec.describe Hashie::Extensions::DeepFind do
let(:hash) do
{
library: {
books: [
{ title: 'Call of the Wild' },
{ title: 'Moby Dick' }
],
shelves: nil,
location: {
address: '123 Library St.',
title: 'Main Library'
}
}
}
end
subject(:instance) { hash.with_indifferent_access.extend(Hashie::Extensions::DeepFind) }
describe '#deep_find' do
it 'indifferently detects a value from a nested hash' do
expect(instance.deep_find(:address)).to eq('123 Library St.')
expect(instance.deep_find('address')).to eq('123 Library St.')
end
it 'indifferently detects a value from a nested array' do
expect(instance.deep_find(:title)).to eq('Call of the Wild')
expect(instance.deep_find('title')).to eq('Call of the Wild')
end
it 'indifferently returns nil if it does not find a match' do
expect(instance.deep_find(:wahoo)).to be_nil
expect(instance.deep_find('wahoo')).to be_nil
end
end
describe '#deep_find_all' do
it 'indifferently detects all values from a nested hash' do
expect(instance.deep_find_all(:title))
.to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
expect(instance.deep_find_all('title'))
.to eq(['Call of the Wild', 'Moby Dick', 'Main Library'])
end
it 'indifferently returns nil if it does not find any matches' do
expect(instance.deep_find_all(:wahoo)).to be_nil
expect(instance.deep_find_all('wahoo')).to be_nil
end
end
end
RSpec.describe Hashie::Extensions::DeepLocate do
let(:hash) do
{
from: 0,
size: 25,
query: {
bool: {
must: [
{
query_string: {
query: 'foobar',
default_operator: 'AND',
fields: [
'title^2',
'_all'
]
}
},
{
match: {
field_1: 'value_1'
}
},
{
range: {
lsr09: {
gte: 2014
}
}
}
],
should: [
{
match: {
field_2: 'value_2'
}
}
],
must_not: [
{
range: {
lsr10: {
gte: 2014
}
}
}
]
}
}
}
end
describe '#deep_locate' do
subject(:instance) { hash.with_indifferent_access.extend(described_class) }
it 'can locate symbolic keys' do
expect(described_class.deep_locate(:lsr10, instance)).to eq ['lsr10' => { 'gte' => 2014 }]
end
it 'can locate string keys' do
expect(described_class.deep_locate('lsr10', instance)).to eq ['lsr10' => { 'gte' => 2014 }]
end
end
end
RSpec.describe Hashie::Extensions::IndifferentAccess do
class Initializable
attr_reader :coerced, :value
def initialize(obj, coerced = nil)
@coerced = coerced
@value = obj.class.to_s
end
def coerced?
!@coerced.nil?
end
end
class Coercable < Initializable
def self.coerce(obj)
new(obj, true)
end
end
class IndifferentHashWithMergeInitializer < Hash
include Hashie::Extensions::MergeInitializer
include Hashie::Extensions::IndifferentAccess
class << self
alias build new
end
end
class IndifferentHashWithArrayInitializer < Hash
include Hashie::Extensions::IndifferentAccess
class << self
alias build []
end
end
class IndifferentHashWithTryConvertInitializer < Hash
include Hashie::Extensions::IndifferentAccess
class << self
alias build try_convert
end
end
class CoercableHash < Hash
include Hashie::Extensions::Coercion
include Hashie::Extensions::MergeInitializer
end
class MashWithIndifferentAccess < Hashie::Mash
include Hashie::Extensions::IndifferentAccess
end
shared_examples_for 'hash with indifferent access' do
it 'is able to access via string or symbol' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(abc: 123)
h = subject.build(indifferent_hash)
expect(h[:abc]).to eq 123
expect(h['abc']).to eq 123
end
describe '#values_at' do
it 'indifferently finds values' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(
:foo => 'bar', 'baz' => 'qux'
)
h = subject.build(indifferent_hash)
expect(h.values_at('foo', :baz)).to eq %w[bar qux]
end
end
describe '#fetch' do
it 'works like normal fetch, but indifferent' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
h = subject.build(indifferent_hash)
expect(h.fetch(:foo)).to eq h.fetch('foo')
expect(h.fetch(:foo)).to eq 'bar'
end
end
describe '#delete' do
it 'deletes indifferently' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(
:foo => 'bar',
'baz' => 'qux'
)
h = subject.build(indifferent_hash)
h.delete('foo')
h.delete(:baz)
expect(h).to be_empty
end
end
describe '#key?' do
let(:h) do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
subject.build(indifferent_hash)
end
it 'finds it indifferently' do
expect(h).to be_key(:foo)
expect(h).to be_key('foo')
end
%w[include? member? has_key?].each do |key_alias|
it "is aliased as #{key_alias}" do
expect(h.send(key_alias.to_sym, :foo)).to be(true)
expect(h.send(key_alias.to_sym, 'foo')).to be(true)
end
end
end
describe '#update' do
let(:h) do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
subject.build(indifferent_hash)
end
it 'allows keys to be indifferent still' do
h.update(baz: 'qux')
expect(h['foo']).to eq 'bar'
expect(h['baz']).to eq 'qux'
end
it 'recursively injects indifference into sub-hashes' do
h.update(baz: { qux: 'abc' })
expect(h['baz']['qux']).to eq 'abc'
end
it 'does not change the ancestors of the injected object class' do
h.update(baz: { qux: 'abc' })
expect({}).not_to be_respond_to(:indifferent_access?)
end
end
describe '#replace' do
let(:h) do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
subject.build(indifferent_hash).replace(bar: 'baz', hi: 'bye')
end
it 'returns self' do
expect(h).to be_a(subject)
end
it 'removes old keys' do
[:foo, 'foo'].each do |k|
expect(h[k]).to be_nil
expect(h.key?(k)).to be_falsy
end
end
it 'creates new keys with indifferent access' do
[:bar, 'bar', :hi, 'hi'].each { |k| expect(h.key?(k)).to be_truthy }
expect(h[:bar]).to eq 'baz'
expect(h['bar']).to eq 'baz'
expect(h[:hi]).to eq 'bye'
expect(h['hi']).to eq 'bye'
end
end
describe '#try_convert' do
describe 'with conversion' do
let(:h) do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: 'bar')
subject.try_convert(indifferent_hash)
end
it 'is a subject' do
expect(h).to be_a(subject)
end
end
describe 'without conversion' do
let(:h) { subject.try_convert('{ :foo => bar }') }
it 'is nil' do
expect(h).to be_nil
end
end
end
end
describe 'with merge initializer' do
subject { IndifferentHashWithMergeInitializer }
it_should_behave_like 'hash with indifferent access'
end
describe 'with array initializer' do
subject { IndifferentHashWithArrayInitializer }
it_should_behave_like 'hash with indifferent access'
end
describe 'with try convert initializer' do
subject { IndifferentHashWithTryConvertInitializer }
it_should_behave_like 'hash with indifferent access'
end
describe 'with coercion' do
subject { CoercableHash }
let(:instance) { subject.new }
it 'supports coercion for ActiveSupport::HashWithIndifferentAccess' do
subject.coerce_key :foo, ActiveSupport::HashWithIndifferentAccess.new(Coercable => Coercable)
instance[:foo] = { 'bar_key' => 'bar_value', 'bar2_key' => 'bar2_value' }
expect(instance[:foo].keys).to all(be_coerced)
expect(instance[:foo].values).to all(be_coerced)
expect(instance[:foo]).to be_a(ActiveSupport::HashWithIndifferentAccess)
end
end
describe 'Mash with indifferent access' do
it 'is able to be created for a deep nested HashWithIndifferentAccess' do
indifferent_hash = ActiveSupport::HashWithIndifferentAccess.new(abc: { def: 123 })
MashWithIndifferentAccess.new(indifferent_hash)
end
end
class DashTestDefaultProc < Hashie::Dash
property :fields, default: -> { [] }
end
describe DashTestDefaultProc do
it 'as_json behaves correctly with default proc' do
object = described_class.new
expect(object.as_json).to be == { 'fields' => [] }
end
end
end

View File

@ -11,9 +11,6 @@ require 'rspec/pending_for'
require './spec/support/ruby_version_check'
require './spec/support/logger'
require 'active_support'
require 'active_support/core_ext'
RSpec.configure do |config|
config.extend RubyVersionCheck
config.expect_with :rspec do |expect|