Add Hashie::Extensions::Mash::SymbolizeKeys
We often have requests to make Mash symbolize keys by default. Since Hashie is used across so many different version of Ruby, we have been hesitant to make this behavior the default. However, there are valid use cases for wanting symbol keys. To satisfy both the needs of those on older Rubies and the needs of those who want symbol keys, this extension gives the end-user the ability to toggle on symbolized keys in their Mash subclasses. By adding this ability, we can wait to implement the symbol keys as a default for a while longer. See #341, #342 for more information. This is a half-measure toward the implementation of #342 (which makes Mash symbolize keys by default).
This commit is contained in:
parent
cc359a22b6
commit
5fa3ebc132
|
@ -12,6 +12,7 @@ scheme are considered to be bugs.
|
|||
|
||||
### Added
|
||||
|
||||
* [#412](https://github.com/intridea/hashie/pull/412): Added a Hashie::Extensions::Mash::SymbolizeKeys extension that overrides the default stringification behavior for keys - [@michaelherold](https://github.com/michaelherold).
|
||||
* Your contribution here.
|
||||
|
||||
### Changed
|
||||
|
|
27
README.md
27
README.md
|
@ -561,6 +561,33 @@ safe_mash.zip = 'Test' # => ArgumentError
|
|||
safe_mash[:zip] = 'test' # => still ArgumentError
|
||||
```
|
||||
|
||||
### Mash Extension:: SymbolizeKeys
|
||||
|
||||
This extension can be mixed into a Mash to change the default behavior of converting keys to strings. After mixing this extension into a Mash, the Mash will convert all keys to symbols.
|
||||
|
||||
```ruby
|
||||
class SymbolizedMash < ::Hashie::Mash
|
||||
include Hashie::Extensions::Mash::SymbolizeKeys
|
||||
end
|
||||
|
||||
symbol_mash = SymbolizedMash.new
|
||||
symbol_mash['test'] = 'value'
|
||||
symbol_mash.test #=> 'value'
|
||||
symbol_mash.to_h #=> {test: 'value'}
|
||||
```
|
||||
|
||||
There is a major benefit and coupled with a major trade-off to this decision (at least on older Rubies). As a benefit, by using symbols as keys, you will be able to use the implicit conversion of a Mash via the `#to_hash` method to destructure (or splat) the contents of a Mash out to a block. This can be handy for doing iterations through the Mash's keys and values, as follows:
|
||||
|
||||
```ruby
|
||||
symbol_mash = SymbolizedMash.new(id: 123, name: 'Rey')
|
||||
symbol_mash.each do |key, value|
|
||||
# key is :id, then :name
|
||||
# value is 123, then 'Rey'
|
||||
end
|
||||
```
|
||||
|
||||
However, on Rubies less than 2.0, this means that every key you send to the Mash will generate a symbol. Since symbols are not garbage-collected on older versions of Ruby, this can cause a slow memory leak when using a symbolized Mash with data generated from user input.
|
||||
|
||||
## Dash
|
||||
|
||||
Dash is an extended Hash that has a discrete set of defined properties and only those properties may be set on the hash. Additionally, you can set defaults for each property. You can also flag a property as required. Required properties will raise an exception if unset. Another option is message for required properties, which allow you to add custom messages for required property.
|
||||
|
|
|
@ -45,6 +45,7 @@ module Hashie
|
|||
|
||||
module Mash
|
||||
autoload :SafeAssignment, 'hashie/extensions/mash/safe_assignment'
|
||||
autoload :SymbolizeKeys, 'hashie/extensions/mash/symbolize_keys'
|
||||
end
|
||||
|
||||
module Array
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
module Hashie
|
||||
module Extensions
|
||||
module Mash
|
||||
# Overrides Mash's default behavior of converting keys to strings
|
||||
#
|
||||
# @example
|
||||
# class LazyResponse < Hashie::Mash
|
||||
# include Hashie::Extensions::Mash::SymbolizedKeys
|
||||
# end
|
||||
#
|
||||
# response = LazyResponse.new("id" => 123, "name" => "Rey").to_h
|
||||
# #=> {id: 123, name: "Rey"}
|
||||
#
|
||||
# @api public
|
||||
module SymbolizeKeys
|
||||
# Hook for being included in a class
|
||||
#
|
||||
# @api private
|
||||
# @return [void]
|
||||
# @raise [ArgumentError] when the base class isn't a Mash
|
||||
def self.included(base)
|
||||
fail ArgumentError, "#{base} must descent from Hashie::Mash" unless base <= Hashie::Mash
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Converts a key to a symbol
|
||||
#
|
||||
# @api private
|
||||
# @param [String, Symbol] key the key to convert to a symbol
|
||||
# @return [void]
|
||||
def convert_key(key)
|
||||
key.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Hashie::Extensions::Mash::SymbolizeKeys do
|
||||
it 'raises an error when included in a class that is not a Mash' do
|
||||
expect do
|
||||
Class.new do
|
||||
include Hashie::Extensions::Mash::SymbolizeKeys
|
||||
end
|
||||
end.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it 'symbolizes all keys in the Mash' do
|
||||
my_mash = Class.new(Hashie::Mash) do
|
||||
include Hashie::Extensions::Mash::SymbolizeKeys
|
||||
end
|
||||
|
||||
expect(my_mash.new('test' => 'value').to_h).to eq(test: 'value')
|
||||
end
|
||||
|
||||
context 'implicit to_hash on double splat' do
|
||||
let(:destructure) { ->(**opts) { opts } }
|
||||
let(:my_mash) do
|
||||
Class.new(Hashie::Mash) do
|
||||
include Hashie::Extensions::Mash::SymbolizeKeys
|
||||
end
|
||||
end
|
||||
let(:instance) { my_mash.new('outer' => { 'inner' => 42 }, 'testing' => [1, 2, 3]) }
|
||||
|
||||
subject { destructure.call(instance) }
|
||||
|
||||
it 'is converted on method calls' do
|
||||
expect(subject).to eq(outer: { inner: 42 }, testing: [1, 2, 3])
|
||||
end
|
||||
|
||||
it 'is converted on explicit operator call' do
|
||||
expect(**instance).to eq(outer: { inner: 42 }, testing: [1, 2, 3])
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue