forked from hashie/hashie
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add an extension to maintain original Mash keys
One of the behaviors of Mash that we see regularly surprise users is that Mash stringifies any keys passed into it. This leads to unexpected lack of synergy between Mash and its cousins (particularly Dash), since the property DSLs do not handle indifferent key access. This extension ensures that the original keys are kept inside the Mash's data structure, at the expense of more costly logic for fetching information indifferently. I have included a benchmark that compares the two. The benchmark shows that when you are passing string keys into a Mash, using this extension will actually be _faster_ than the default implementation, but that the reverse is true when passing symbol keys. In hashie#296, I tried to do this universally for all Mashes, which slowed down the fetching behavior for Mash significantly. I like this attempt much better because it allows users to opt into the new behavior if they want it, while still keeping the default implementation as-is. Fixes hashie#196 by giving the option of keeping the original structure of the Mash when using it with Dash. Fixes hashie#246 by giving the option of opting into keeping the original keys. Closes hashie#296 by giving a more flexible path forward that doesn't change the semantics of the main Mash class.
- Loading branch information
1 parent
b36de94
commit bb61d7e
Showing
9 changed files
with
177 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
require_relative '../lib/hashie' | ||
require 'benchmark/ips' | ||
|
||
class KeepingMash < Hashie::Mash | ||
include Hashie::Extensions::Mash::KeepOriginalKeys | ||
end | ||
|
||
original = { test: 'value' } | ||
mash = Hashie::Mash.new(original) | ||
keeping_mash = KeepingMash.new(original) | ||
|
||
Benchmark.ips do |x| | ||
x.report('keep symbol') { keeping_mash.test } | ||
x.report('normal symbol') { mash.test } | ||
|
||
x.compare! | ||
end | ||
|
||
original = { 'test' => 'value' } | ||
mash = Hashie::Mash.new(original) | ||
keeping_mash = KeepingMash.new(original) | ||
|
||
Benchmark.ips do |x| | ||
x.report('keep string') { keeping_mash.test } | ||
x.report('normal string') { mash.test } | ||
|
||
x.compare! | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
module Hashie | ||
module Extensions | ||
module Mash | ||
# Overrides the indifferent access of a Mash to keep keys in the | ||
# original format given to the Mash. | ||
# | ||
# @example | ||
# class KeepingMash < Hashie::Mash | ||
# include Hashie::Extensions::Mash::KeepOriginalKeys | ||
# end | ||
# | ||
# mash = KeepingMash.new(:symbol_key => :symbol, 'string_key' => 'string') | ||
# mash.to_hash #=> { :symbol_key => :symbol, 'string_key' => 'string' } | ||
# mash['string_key'] == mash[:string_key] #=> true | ||
# mash[:symbol_key] == mash['symbol_key'] #=> true | ||
module KeepOriginalKeys | ||
private | ||
|
||
def self.included(descendant) | ||
unless descendant <= Hashie::Mash | ||
fail ArgumentError, "#{descendant} is not a kind of Hashie::Mash" | ||
end | ||
end | ||
|
||
# Converts the key when necessary to access the correct Mash key. | ||
# | ||
# @param [Object, String, Symbol] key the key to access. | ||
# @return [Object] the value assigned to the key. | ||
def convert_key(key) | ||
if regular_key?(key) | ||
key | ||
elsif (converted_key = __convert(key)) && regular_key?(converted_key) | ||
converted_key | ||
else | ||
key | ||
end | ||
end | ||
|
||
# Converts symbol/string keys to their alternative formats, but leaves | ||
# other keys alone. | ||
# | ||
# @param [Object, String, Symbol] key the key to convert. | ||
# @return [Object, String, Symbol] the converted key. | ||
def __convert(key) | ||
case key | ||
when Symbol then key.to_s | ||
when String then key.to_sym | ||
else key | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
require 'spec_helper' | ||
|
||
RSpec.describe Hashie::Extensions::Mash::KeepOriginalKeys do | ||
let(:keeping_mash) do | ||
Class.new(Hashie::Mash) do | ||
include Hashie::Extensions::Mash::KeepOriginalKeys | ||
end | ||
end | ||
|
||
it 'keeps the keys in the resulting hash identical to the original' do | ||
original = { :a => 'apple', 'b' => 'bottle' } | ||
mash = keeping_mash.new(original) | ||
|
||
expect(mash.to_hash).to eq(original) | ||
end | ||
|
||
it 'indifferently responds to keys' do | ||
original = { :a => 'apple', 'b' => 'bottle' } | ||
mash = keeping_mash.new(original) | ||
|
||
expect(mash['a']).to eq(mash[:a]) | ||
expect(mash['b']).to eq(mash[:b]) | ||
end | ||
|
||
it 'responds to all method accessors like a Mash' do | ||
original = { :a => 'apple', 'b' => 'bottle' } | ||
mash = keeping_mash.new(original) | ||
|
||
expect(mash.a).to eq('apple') | ||
expect(mash.a?).to eq(true) | ||
expect(mash.b).to eq('bottle') | ||
expect(mash.b?).to eq(true) | ||
expect(mash.underbang_).to be_a(keeping_mash) | ||
expect(mash.bang!).to be_a(keeping_mash) | ||
expect(mash.predicate?).to eq(false) | ||
end | ||
|
||
it 'keeps the keys that are directly passed without converting them' do | ||
original = { :a => 'apple', 'b' => 'bottle' } | ||
mash = keeping_mash.new(original) | ||
|
||
mash[:c] = 'cat' | ||
mash['d'] = 'dog' | ||
expect(mash.to_hash).to eq(:a => 'apple', 'b' => 'bottle', :c => 'cat', 'd' => 'dog') | ||
end | ||
end |