diff options
-rw-r--r-- | .rubocop_todo.yml | 10 | ||||
-rw-r--r-- | CHANGELOG.md | 3 | ||||
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | UPGRADING.md | 6 | ||||
-rw-r--r-- | lib/hashie.rb | 38 | ||||
-rw-r--r-- | lib/hashie/extensions/key_conflict_warning.rb | 55 | ||||
-rw-r--r-- | lib/hashie/mash.rb | 66 | ||||
-rw-r--r-- | spec/hashie/mash_spec.rb | 23 |
8 files changed, 131 insertions, 77 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b3fa0d3..0590878 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2019-08-13 23:33:30 -0400 using RuboCop version 0.52.1. +# on 2019-10-10 00:07:29 -0400 using RuboCop version 0.52.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -14,6 +14,12 @@ Metrics/AbcSize: Metrics/CyclomaticComplexity: Max: 11 +# Offense count: 1 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. +# URISchemes: http, https +Metrics/LineLength: + Max: 111 + # Offense count: 18 # Configuration parameters: CountComments. Metrics/MethodLength: @@ -23,6 +29,6 @@ Metrics/MethodLength: Metrics/PerceivedComplexity: Max: 10 -# Offense count: 39 +# Offense count: 41 Style/Documentation: Enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ac655..a2b2c37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,8 @@ scheme are considered to be bugs. * [#323](https://github.com/intridea/hashie/pull/323): Added `Hashie::Extensions::Mash::DefineAccessors` - [@marshall-lee](https://github.com/marshall-lee). * [#474](https://github.com/intridea/hashie/pull/474): Expose `YAML#safe_load` options in `Mash#load` - [@riouruma](https://github.com/riouruma), [@dblock](https://github.com/dblock). * [#478](https://github.com/intridea/hashie/pull/478): Added optional array parameter to `Hashie::Mash.disable_warnings` - [@bobbymcwho](https://github.com/bobbymcwho). -* [#481](https://github.com/intridea/hashie/pull/481): Ruby 2.6 - Support Hash#merge and #merge! called with multiple Hashes/Mashes - [@bobbymcwho](https://github.com/bobbymcwho). +* [#481](https://github.com/intridea/hashie/pull/481): Ruby 2.6 - Support `Hash#merge` and `#merge!` called with multiple Hashes/Mashes - [@bobbymcwho](https://github.com/bobbymcwho). +* [#488](https://github.com/intridea/hashie/pull/488): Added ability to create an anonymous `Hashie::Mash` subclass with key conflict errors silenced using `Hashie::Mash.quiet.new` - [@bobbymcwho](https://github.com/bobbymcwho). * Your contribution here. ### Changed @@ -557,6 +557,13 @@ end Response.new(merge: 'true', compact: true, zip: '90210', zap: 'electric') ``` +If you would like to create an anonymous subclass of a Hashie::Mash with key conflict warnings disabled: + +```ruby +Hashie::Mash.quiet.new(zip: '90210', compact: true) # no errors logged +Hashie::Mash.quiet(:zip).new(zip: '90210', compact: true) # error logged for compact +``` + ### How does the wrapping of Mash sub-Hashes work? Mash duplicates any sub-Hashes that you add to it and wraps them in a Mash. This allows for infinite chaining of nested Hashes within a Mash without modifying the object(s) that are passed into the Mash. When you subclass Mash, the subclass wraps any sub-Hashes in its own class. This preserves any extensions that you mixed into the Mash subclass and allows them to work within the sub-Hashes, in addition to the main containing Mash. diff --git a/UPGRADING.md b/UPGRADING.md index ce01eca..1374e10 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -36,10 +36,14 @@ p cool_parents This may make places where you had to re-make the Mash redundant, and may cause unintended side effects if your application was expecting a plain old ruby Hash. -### Ruby 2.6: Mash#merge and Mash#merge! +#### Ruby 2.6: Mash#merge and Mash#merge! In Ruby > 2.6.0, Hashie now supports passing multiple hash and Mash objects to Mash#merge and Mash#merge!. +#### Hashie::Mash::CannotDisableMashWarnings error class is removed + +There shouldn't really be a case that anyone was relying on catching this specific error, but if so, they should change it to rescue Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings + ### Upgrading to 3.7.0 #### Mash#load takes options diff --git a/lib/hashie.rb b/lib/hashie.rb index fd93e05..a3fc11c 100644 --- a/lib/hashie.rb +++ b/lib/hashie.rb @@ -12,26 +12,26 @@ module Hashie autoload :Utils, 'hashie/utils' module Extensions - autoload :Coercion, 'hashie/extensions/coercion' - autoload :DeepMerge, 'hashie/extensions/deep_merge' - autoload :IgnoreUndeclared, 'hashie/extensions/ignore_undeclared' - autoload :IndifferentAccess, 'hashie/extensions/indifferent_access' - autoload :MergeInitializer, 'hashie/extensions/merge_initializer' - autoload :MethodAccess, 'hashie/extensions/method_access' - autoload :MethodQuery, 'hashie/extensions/method_access' - autoload :MethodReader, 'hashie/extensions/method_access' - autoload :MethodWriter, 'hashie/extensions/method_access' - autoload :StringifyKeys, 'hashie/extensions/stringify_keys' - autoload :SymbolizeKeys, 'hashie/extensions/symbolize_keys' - autoload :DeepFetch, 'hashie/extensions/deep_fetch' - autoload :DeepFind, 'hashie/extensions/deep_find' - autoload :DeepLocate, 'hashie/extensions/deep_locate' - autoload :PrettyInspect, 'hashie/extensions/pretty_inspect' - autoload :KeyConversion, 'hashie/extensions/key_conversion' + autoload :Coercion, 'hashie/extensions/coercion' + autoload :DeepMerge, 'hashie/extensions/deep_merge' + autoload :IgnoreUndeclared, 'hashie/extensions/ignore_undeclared' + autoload :IndifferentAccess, 'hashie/extensions/indifferent_access' + autoload :MergeInitializer, 'hashie/extensions/merge_initializer' + autoload :MethodAccess, 'hashie/extensions/method_access' + autoload :MethodQuery, 'hashie/extensions/method_access' + autoload :MethodReader, 'hashie/extensions/method_access' + autoload :MethodWriter, 'hashie/extensions/method_access' + autoload :StringifyKeys, 'hashie/extensions/stringify_keys' + autoload :SymbolizeKeys, 'hashie/extensions/symbolize_keys' + autoload :DeepFetch, 'hashie/extensions/deep_fetch' + autoload :DeepFind, 'hashie/extensions/deep_find' + autoload :DeepLocate, 'hashie/extensions/deep_locate' + 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' - autoload :RubyVersion, 'hashie/extensions/ruby_version' - autoload :RubyVersionCheck, 'hashie/extensions/ruby_version_check' + autoload :StrictKeyAccess, 'hashie/extensions/strict_key_access' + autoload :RubyVersion, 'hashie/extensions/ruby_version' + autoload :RubyVersionCheck, 'hashie/extensions/ruby_version_check' module Parsers autoload :YamlErbParser, 'hashie/extensions/parsers/yaml_erb_parser' diff --git a/lib/hashie/extensions/key_conflict_warning.rb b/lib/hashie/extensions/key_conflict_warning.rb new file mode 100644 index 0000000..7ce56a1 --- /dev/null +++ b/lib/hashie/extensions/key_conflict_warning.rb @@ -0,0 +1,55 @@ +module Hashie + module Extensions + module KeyConflictWarning + class CannotDisableMashWarnings < StandardError + def initialize + super( + 'You cannot disable warnings on the base Mash class. ' \ + 'Please subclass the Mash and disable it in the subclass.' + ) + end + end + + # Disable the logging of warnings based on keys conflicting keys/methods + # + # @api semipublic + # @return [void] + def disable_warnings(*method_keys) + raise CannotDisableMashWarnings if self == Hashie::Mash + if method_keys.any? + disabled_warnings.concat(method_keys).tap(&:flatten!).uniq! + else + disabled_warnings.clear + end + + @disable_warnings = true + end + + # Checks whether this class disables warnings for conflicting keys/methods + # + # @api semipublic + # @return [Boolean] + def disable_warnings?(method_key = nil) + return disabled_warnings.include?(method_key) if disabled_warnings.any? && method_key + @disable_warnings ||= false + end + + # Returns an array of blacklisted methods that this class disables warnings for. + # + # @api semipublic + # @return [Boolean] + def disabled_warnings + @_disabled_warnings ||= [] + end + + # Inheritance hook that sets class configuration when inherited. + # + # @api semipublic + # @return [void] + def inherited(subclass) + super + subclass.disable_warnings(disabled_warnings) if disable_warnings? + end + end + end +end diff --git a/lib/hashie/mash.rb b/lib/hashie/mash.rb index 888b4f6..77a852f 100644 --- a/lib/hashie/mash.rb +++ b/lib/hashie/mash.rb @@ -2,6 +2,7 @@ require 'hashie/hash' require 'hashie/array' require 'hashie/utils' require 'hashie/logger' +require 'hashie/extensions/key_conflict_warning' module Hashie # Mash allows you to create pseudo-objects that have method-like @@ -62,59 +63,10 @@ module Hashie class Mash < Hash include Hashie::Extensions::PrettyInspect include Hashie::Extensions::RubyVersionCheck + extend Hashie::Extensions::KeyConflictWarning ALLOWED_SUFFIXES = %w[? ! = _].freeze - class CannotDisableMashWarnings < StandardError - def initialize - super( - 'You cannot disable warnings on the base Mash class. ' \ - 'Please subclass the Mash and disable it in the subclass.' - ) - end - end - - # Disable the logging of warnings based on keys conflicting keys/methods - # - # @api semipublic - # @return [void] - def self.disable_warnings(*method_keys) - raise CannotDisableMashWarnings if self == Hashie::Mash - if method_keys.any? - disable_warnings_blacklist.concat(method_keys).tap(&:flatten!).uniq! - else - disable_warnings_blacklist.clear - end - - @disable_warnings = true - end - - # Checks whether this class disables warnings for conflicting keys/methods - # - # @api semipublic - # @return [Boolean] - def self.disable_warnings?(method_key = nil) - return disable_warnings_blacklist.include?(method_key) if disable_warnings_blacklist.any? && method_key - @disable_warnings ||= false - end - - # Returns an array of blacklisted methods that this class disables warnings for. - # - # @api semipublic - # @return [Boolean] - def self.disable_warnings_blacklist - @_disable_warnings_blacklist ||= [] - end - - # Inheritance hook that sets class configuration when inherited. - # - # @api semipublic - # @return [void] - def self.inherited(subclass) - super - subclass.disable_warnings(disable_warnings_blacklist) if disable_warnings? - end - def self.load(path, options = {}) @_mashes ||= new @@ -149,6 +101,20 @@ module Hashie default ? super(default) : super(&blk) end + # Creates a new anonymous subclass with key conflict + # warnings disabled. You may pass an array of method + # symbols to restrict the warnings blacklist to. + # Hashie::Mash.quiet.new(hash) all warnings disabled. + # Hashie::Mash.quiet(:zip).new(hash) only zip warning + # is disabled. + def self.quiet(*method_keys) + (@memoized_classes ||= {})[method_keys] || + Class.new(self).tap do |k| + k.send(:disable_warnings, *method_keys) + @memoized_classes[method_keys] = k + end + end + class << self; alias [] new; end alias regular_reader [] diff --git a/spec/hashie/mash_spec.rb b/spec/hashie/mash_spec.rb index e2a178c..77a1c6b 100644 --- a/spec/hashie/mash_spec.rb +++ b/spec/hashie/mash_spec.rb @@ -160,7 +160,9 @@ describe Hashie::Mash do end it 'cannot disable logging on the base Mash' do - expect { Hashie::Mash.disable_warnings }.to raise_error(Hashie::Mash::CannotDisableMashWarnings) + expected_error = Hashie::Extensions::KeyConflictWarning::CannotDisableMashWarnings + + expect { Hashie::Mash.disable_warnings }.to raise_error(expected_error) end it 'carries over the disable for warnings on grandchild classes' do @@ -200,7 +202,7 @@ describe Hashie::Mash do grandchild_class.new('address' => { 'zip' => '90210' }, 'merge' => true) - expect(grandchild_class.disable_warnings_blacklist).to eq(%i[zip merge]) + expect(grandchild_class.disabled_warnings).to eq(%i[zip merge]) expect(logger_output).to be_blank end @@ -213,7 +215,7 @@ describe Hashie::Mash do disable_warnings :cycle end - expect(child_class.disable_warnings_blacklist).to eq(%i[zip merge cycle]) + expect(child_class.disabled_warnings).to eq(%i[zip merge cycle]) end end @@ -226,7 +228,7 @@ describe Hashie::Mash do child_class.new('address' => { 'zip' => '90210' }, 'merge' => true, 'cycle' => 'bi') - expect(child_class.disable_warnings_blacklist).to eq([]) + expect(child_class.disabled_warnings).to eq([]) expect(logger_output).to be_blank end end @@ -962,6 +964,19 @@ describe Hashie::Mash do end end + describe '.quiet' do + it 'returns a subclass of the calling class' do + expect(Hashie::Mash.quiet.new).to be_a(Hashie::Mash) + end + + it 'memoizes and returns classes' do + call_one = Hashie::Mash.quiet + call_two = Hashie::Mash.quiet + expect(Hashie::Mash.instance_variable_get('@memoized_classes').count).to eq(1) + expect(call_one).to eq(call_two) + end + end + with_minimum_ruby('2.3.0') do describe '#dig' do subject { described_class.new(a: { b: 1 }) } |