summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby McDonald <bobbymcwho@gmail.com>2019-08-13 23:38:06 -0400
committerBobby McDonald <bobbymcwho@gmail.com>2019-08-14 12:31:10 -0400
commit3e64a4f58d24b4741209b0678dbf62c00fe6cd03 (patch)
tree33fb8d9e9dfae87274f8dff1f4d7a2fc18e31dc7
parent1de19bff87e1f75192af2788e8ee47cda593ade2 (diff)
downloadhashie-3e64a4f58d24b4741209b0678dbf62c00fe6cd03.tar.gz
Implement non-destructive hash methods
When calling the following non-destructive hash methods: :compact :invert :reject :select :slice :transform_keys :transform_values we would be returned an instance of a standard Hash rather than a Mash (or subclass). This changes that behavior to instead return an instance of the class the method was called on.
-rw-r--r--.rubocop_todo.yml6
-rw-r--r--lib/hashie/mash.rb42
-rw-r--r--spec/hashie/mash_spec.rb151
3 files changed, 196 insertions, 3 deletions
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index ab2db99..bc15a46 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
-# on 2019-07-17 09:23:49 -0400 using RuboCop version 0.52.1.
+# on 2019-08-13 23:33:30 -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
@@ -13,13 +13,13 @@ Metrics/AbcSize:
# Offense count: 2
# Configuration parameters: CountComments.
Metrics/ClassLength:
- Max: 221
+ Max: 266
# Offense count: 7
Metrics/CyclomaticComplexity:
Max: 11
-# Offense count: 19
+# Offense count: 18
# Configuration parameters: CountComments.
Metrics/MethodLength:
Max: 28
diff --git a/lib/hashie/mash.rb b/lib/hashie/mash.rb
index 06cef59..bac93a4 100644
--- a/lib/hashie/mash.rb
+++ b/lib/hashie/mash.rb
@@ -207,6 +207,31 @@ module Hashie
super(*keys.map { |key| convert_key(key) })
end
+ # Returns a new instance of the class it was called on, with nil values
+ # removed.
+ def compact
+ self.class.new(super)
+ end
+
+ # Returns a new instance of the class it was called on, using its keys as
+ # values, and its values as keys. The new values and keys will always be
+ # strings.
+ def invert
+ self.class.new(super)
+ end
+
+ # Returns a new instance of the class it was called on, containing elements
+ # for which the given block returns false.
+ def reject(&blk)
+ self.class.new(super(&blk))
+ end
+
+ # Returns a new instance of the class it was called on, containing elements
+ # for which the given block returns true.
+ def select(&blk)
+ self.class.new(super(&blk))
+ end
+
alias regular_dup dup
# Duplicates the current mash as a new mash.
def dup
@@ -320,6 +345,23 @@ module Hashie
end
end
+ with_minimum_ruby('2.4.0') do
+ def transform_values(&blk)
+ self.class.new(super(&blk))
+ end
+ end
+
+ with_minimum_ruby('2.5.0') do
+ def slice(*keys)
+ string_keys = keys.map { |key| convert_key(key) }
+ self.class.new(super(*string_keys))
+ end
+
+ def transform_keys(&blk)
+ self.class.new(super(&blk))
+ end
+ end
+
protected
def method_name_and_suffix(method_name)
diff --git a/spec/hashie/mash_spec.rb b/spec/hashie/mash_spec.rb
index 3bcb478..837a7f1 100644
--- a/spec/hashie/mash_spec.rb
+++ b/spec/hashie/mash_spec.rb
@@ -878,6 +878,90 @@ describe Hashie::Mash do
end
end
+ describe '#compact' do
+ subject(:mash) { described_class.new(a: 1, b: nil) }
+
+ it 'returns a Hashie::Mash' do
+ expect(mash.compact).to be_kind_of(described_class)
+ end
+
+ it 'removes keys with nil values' do
+ expect(mash.compact).to eq('a' => 1)
+ end
+
+ context 'when using with subclass' do
+ let(:subclass) { Class.new(Hashie::Mash) }
+ subject(:sub_mash) { subclass.new(a: 1, b: nil) }
+
+ it 'creates an instance of subclass' do
+ expect(sub_mash.compact).to be_kind_of(subclass)
+ end
+ end
+ end
+
+ describe '#invert' do
+ subject(:mash) { described_class.new(a: 'apple', b: 4) }
+
+ it 'returns a Hashie::Mash' do
+ expect(mash.invert).to be_kind_of(described_class)
+ end
+
+ it 'returns a mash with the keys and values inverted' do
+ expect(mash.invert).to eq('apple' => 'a', '4' => 'b')
+ end
+
+ context 'when using with subclass' do
+ let(:subclass) { Class.new(Hashie::Mash) }
+ subject(:sub_mash) { subclass.new(a: 1, b: nil) }
+
+ it 'creates an instance of subclass' do
+ expect(sub_mash.invert).to be_kind_of(subclass)
+ end
+ end
+ end
+
+ describe '#reject' do
+ subject(:mash) { described_class.new(a: 1, b: nil) }
+
+ it 'returns a Hashie::Mash' do
+ expect(mash.reject { |_k, v| v.nil? }).to be_kind_of(described_class)
+ end
+
+ it 'rejects keys for which the block returns true' do
+ expect(mash.reject { |_k, v| v.nil? }).to eq('a' => 1)
+ end
+
+ context 'when using with subclass' do
+ let(:subclass) { Class.new(Hashie::Mash) }
+ subject(:sub_mash) { subclass.new(a: 1, b: nil) }
+
+ it 'creates an instance of subclass' do
+ expect(sub_mash.reject { |_k, v| v.nil? }).to be_kind_of(subclass)
+ end
+ end
+ end
+
+ describe '#select' do
+ subject(:mash) { described_class.new(a: 'apple', b: 4) }
+
+ it 'returns a Hashie::Mash' do
+ expect(mash.select { |_k, v| v.is_a? String }).to be_kind_of(described_class)
+ end
+
+ it 'selects keys for which the block returns true' do
+ expect(mash.select { |_k, v| v.is_a? String }).to eq('a' => 'apple')
+ end
+
+ context 'when using with subclass' do
+ let(:subclass) { Class.new(Hashie::Mash) }
+ subject(:sub_mash) { subclass.new(a: 1, b: nil) }
+
+ it 'creates an instance of subclass' do
+ expect(sub_mash.select { |_k, v| v.is_a? String }).to be_kind_of(subclass)
+ end
+ end
+ end
+
with_minimum_ruby('2.3.0') do
describe '#dig' do
subject { described_class.new(a: { b: 1 }) }
@@ -895,4 +979,71 @@ describe Hashie::Mash do
end
end
end
+
+ with_minimum_ruby('2.4.0') do
+ describe '#transform_values' do
+ subject(:mash) { described_class.new(a: 1) }
+
+ it 'returns a Hashie::Mash' do
+ expect(mash.transform_values(&:to_s)).to be_kind_of(described_class)
+ end
+
+ it 'transforms the value' do
+ expect(mash.transform_values(&:to_s).a).to eql('1')
+ end
+
+ context 'when using with subclass' do
+ let(:subclass) { Class.new(Hashie::Mash) }
+ subject(:sub_mash) { subclass.new(a: 1).transform_values { |a| a + 2 } }
+
+ it 'creates an instance of subclass' do
+ expect(sub_mash).to be_kind_of(subclass)
+ end
+ end
+ end
+ end
+
+ with_minimum_ruby('2.5.0') do
+ describe '#slice' do
+ subject(:mash) { described_class.new(a: 1, b: 2) }
+
+ it 'returns a Hashie::Mash' do
+ expect(mash.slice(:a)).to be_kind_of(described_class)
+ end
+
+ it 'returns a Mash with only the keys passed' do
+ expect(mash.slice(:a).to_hash).to eq('a' => 1)
+ end
+
+ context 'when using with subclass' do
+ let(:subclass) { Class.new(Hashie::Mash) }
+ subject(:sub_mash) { subclass.new(a: 1, b: 2) }
+
+ it 'creates an instance of subclass' do
+ expect(sub_mash.slice(:a)).to be_kind_of(subclass)
+ end
+ end
+ end
+
+ describe '#transform_keys' do
+ subject(:mash) { described_class.new(a: 1, b: 2) }
+
+ it 'returns a Hashie::Mash' do
+ expect(mash.transform_keys { |k| k + k }).to be_kind_of(described_class)
+ end
+
+ it 'returns a Mash with transformed keys' do
+ expect(mash.transform_keys { |k| k + k }).to eq('aa' => 1, 'bb' => 2)
+ end
+
+ context 'when using with subclass' do
+ let(:subclass) { Class.new(Hashie::Mash) }
+ subject(:sub_mash) { subclass.new(a: 1, b: 2) }
+
+ it 'creates an instance of subclass' do
+ expect(sub_mash.transform_keys { |k| k + k }).to be_kind_of(subclass)
+ end
+ end
+ end
+ end
end