hashie 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +16 -0
- data/CONTRIBUTING.md +27 -0
- data/Guardfile +1 -1
- data/README.markdown +240 -0
- data/VERSION +1 -0
- data/hashie.gemspec +4 -2
- data/lib/hashie.rb +20 -6
- data/lib/hashie/dash.rb +21 -8
- data/lib/hashie/extensions/coercion.rb +105 -0
- data/lib/hashie/extensions/deep_merge.rb +21 -0
- data/lib/hashie/extensions/indifferent_access.rb +114 -0
- data/lib/hashie/extensions/key_conversion.rb +92 -0
- data/lib/hashie/extensions/merge_initializer.rb +26 -0
- data/lib/hashie/extensions/method_access.rb +124 -0
- data/lib/hashie/extensions/structure.rb +47 -0
- data/lib/hashie/hash.rb +12 -6
- data/lib/hashie/mash.rb +48 -17
- data/lib/hashie/trash.rb +41 -5
- data/lib/hashie/version.rb +1 -1
- data/spec/hashie/dash_spec.rb +25 -0
- data/spec/hashie/extensions/coercion_spec.rb +89 -0
- data/spec/hashie/extensions/deep_merge_spec.rb +20 -0
- data/spec/hashie/extensions/indifferent_access_spec.rb +74 -0
- data/spec/hashie/extensions/key_conversion_spec.rb +102 -0
- data/spec/hashie/extensions/merge_initializer_spec.rb +20 -0
- data/spec/hashie/extensions/method_access_spec.rb +112 -0
- data/spec/hashie/hash_spec.rb +2 -12
- data/spec/hashie/mash_spec.rb +105 -17
- data/spec/hashie/trash_spec.rb +75 -0
- metadata +72 -24
- data/Gemfile.lock +0 -34
- data/README.rdoc +0 -120
@@ -0,0 +1,21 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
module DeepMerge
|
4
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
5
|
+
def deep_merge(other_hash)
|
6
|
+
(class << (h = dup); self; end).send :include, Hashie::Extensions::DeepMerge
|
7
|
+
h.deep_merge!(other_hash)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
11
|
+
# Modifies the receiver in place.
|
12
|
+
def deep_merge!(other_hash)
|
13
|
+
other_hash.each do |k,v|
|
14
|
+
(class << (tv = self[k]); self; end).send :include, Hashie::Extensions::DeepMerge
|
15
|
+
self[k] = tv.is_a?(::Hash) && v.is_a?(::Hash) ? tv.deep_merge(v) : v
|
16
|
+
end
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
# IndifferentAccess gives you the ability to not care
|
4
|
+
# whether your hash has string or symbol keys. Made famous
|
5
|
+
# in Rails for accessing query and POST parameters, this
|
6
|
+
# is a handy tool for making sure your hash has maximum
|
7
|
+
# utility.
|
8
|
+
#
|
9
|
+
# One unique feature of this mixin is that it will recursively
|
10
|
+
# inject itself into sub-hash instances without modifying
|
11
|
+
# the actual class of the sub-hash.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# class MyHash < Hash
|
15
|
+
# include Hashie::Extensions::MergeInitializer
|
16
|
+
# include Hashie::Extensions::IndifferentAccess
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# h = MyHash.new(:foo => 'bar', 'baz' => 'blip')
|
20
|
+
# h['foo'] # => 'bar'
|
21
|
+
# h[:foo] # => 'bar'
|
22
|
+
# h[:baz] # => 'blip'
|
23
|
+
# h['baz'] # => 'blip'
|
24
|
+
#
|
25
|
+
module IndifferentAccess
|
26
|
+
def self.included(base)
|
27
|
+
base.class_eval do
|
28
|
+
alias_method :regular_writer, :[]=
|
29
|
+
alias_method :[]=, :indifferent_writer
|
30
|
+
%w(default update fetch delete key? values_at).each do |m|
|
31
|
+
alias_method "regular_#{m}", m
|
32
|
+
alias_method m, "indifferent_#{m}"
|
33
|
+
end
|
34
|
+
|
35
|
+
%w(include? member? has_key?).each do |key_alias|
|
36
|
+
alias_method key_alias, :indifferent_key?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# This will inject indifferent access into an instance of
|
42
|
+
# a hash without modifying the actual class. This is what
|
43
|
+
# allows IndifferentAccess to spread to sub-hashes.
|
44
|
+
def self.inject!(hash)
|
45
|
+
(class << hash; self; end).send :include, IndifferentAccess
|
46
|
+
hash.convert!
|
47
|
+
end
|
48
|
+
|
49
|
+
# Injects indifferent access into a duplicate of the hash
|
50
|
+
# provided. See #inject!
|
51
|
+
def self.inject(hash)
|
52
|
+
inject!(hash.dup)
|
53
|
+
end
|
54
|
+
|
55
|
+
def convert_key(key)
|
56
|
+
key.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
# Iterates through the keys and values, reconverting them to
|
60
|
+
# their proper indifferent state. Used when IndifferentAccess
|
61
|
+
# is injecting itself into member hashes.
|
62
|
+
def convert!
|
63
|
+
keys.each do |k|
|
64
|
+
regular_writer convert_key(k), convert_value(self.regular_delete(k))
|
65
|
+
end
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def convert_value(value)
|
70
|
+
if hash_lacking_indifference?(value)
|
71
|
+
IndifferentAccess.inject(value.dup)
|
72
|
+
elsif value.is_a?(::Array)
|
73
|
+
value.dup.replace(value.map { |e| convert_value(e) })
|
74
|
+
else
|
75
|
+
value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def indifferent_default(key = nil)
|
80
|
+
return self[convert_key(key)] if key?(key)
|
81
|
+
regular_default(key)
|
82
|
+
end
|
83
|
+
|
84
|
+
def indifferent_update(other_hash)
|
85
|
+
return regular_update(other_hash) if hash_with_indifference?(other_hash)
|
86
|
+
other_hash.each_pair do |k,v|
|
87
|
+
self[k] = v
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def indifferent_writer(key, value); regular_writer convert_key(key), convert_value(value) end
|
92
|
+
def indifferent_fetch(key, *args); regular_fetch convert_key(key), *args end
|
93
|
+
def indifferent_delete(key); regular_delete convert_key(key) end
|
94
|
+
def indifferent_key?(key); regular_key? convert_key(key) end
|
95
|
+
def indifferent_values_at(*indices); indices.map{|i| self[i] } end
|
96
|
+
|
97
|
+
def indifferent_access?; true end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
def hash_lacking_indifference?(other)
|
102
|
+
other.is_a?(::Hash) &&
|
103
|
+
!(other.respond_to?(:indifferent_access?) &&
|
104
|
+
other.indifferent_access?)
|
105
|
+
end
|
106
|
+
|
107
|
+
def hash_with_indifference?(other)
|
108
|
+
other.is_a?(::Hash) &&
|
109
|
+
other.respond_to?(:indifferent_access?) &&
|
110
|
+
other.indifferent_access?
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
module StringifyKeys
|
4
|
+
# Convert all keys in the hash to strings.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# test = {:abc => 'def'}
|
8
|
+
# test.stringify_keys!
|
9
|
+
# test # => {'abc' => 'def'}
|
10
|
+
def stringify_keys!
|
11
|
+
keys.each do |k|
|
12
|
+
stringify_keys_recursively!(self[k])
|
13
|
+
self[k.to_s] = self.delete(k)
|
14
|
+
end
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return a new hash with all keys converted
|
19
|
+
# to strings.
|
20
|
+
def stringify_keys
|
21
|
+
dup.stringify_keys!
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
# Stringify all keys recursively within nested
|
27
|
+
# hashes and arrays.
|
28
|
+
def stringify_keys_recursively!(object)
|
29
|
+
if self.class === object
|
30
|
+
object.stringify_keys!
|
31
|
+
elsif ::Array === object
|
32
|
+
object.each do |i|
|
33
|
+
stringify_keys_recursively!(i)
|
34
|
+
end
|
35
|
+
object
|
36
|
+
elsif object.respond_to?(:stringify_keys!)
|
37
|
+
object.stringify_keys!
|
38
|
+
else
|
39
|
+
object
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module SymbolizeKeys
|
45
|
+
# Convert all keys in the hash to symbols.
|
46
|
+
#
|
47
|
+
# @example
|
48
|
+
# test = {'abc' => 'def'}
|
49
|
+
# test.symbolize_keys!
|
50
|
+
# test # => {:abc => 'def'}
|
51
|
+
def symbolize_keys!
|
52
|
+
keys.each do |k|
|
53
|
+
symbolize_keys_recursively!(self[k])
|
54
|
+
self[k.to_sym] = self.delete(k)
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return a new hash with all keys converted
|
60
|
+
# to symbols.
|
61
|
+
def symbolize_keys
|
62
|
+
dup.symbolize_keys!
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
# Symbolize all keys recursively within nested
|
68
|
+
# hashes and arrays.
|
69
|
+
def symbolize_keys_recursively!(object)
|
70
|
+
if self.class === object
|
71
|
+
object.symbolize_keys!
|
72
|
+
elsif ::Array === object
|
73
|
+
object.each do |i|
|
74
|
+
symbolize_keys_recursively!(i)
|
75
|
+
end
|
76
|
+
object
|
77
|
+
elsif object.respond_to?(:symbolize_keys!)
|
78
|
+
object.symbolize_keys!
|
79
|
+
else
|
80
|
+
object
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module KeyConversion
|
86
|
+
def self.included(base)
|
87
|
+
base.send :include, SymbolizeKeys
|
88
|
+
base.send :include, StringifyKeys
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
# The MergeInitializer is a super-simple mixin that allows
|
4
|
+
# you to initialize a subclass of Hash with another Hash
|
5
|
+
# to give you faster startup time for Hash subclasses. Note
|
6
|
+
# that you can still provide a default value as a second
|
7
|
+
# argument to the initializer.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# class MyHash < Hash
|
11
|
+
# include Hashie::Extensions::MergeInitializer
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# h = MyHash.new(:abc => 'def')
|
15
|
+
# h[:abc] # => 'def'
|
16
|
+
#
|
17
|
+
module MergeInitializer
|
18
|
+
def initialize(hash = {}, default = nil, &block)
|
19
|
+
default ? super(default) : super(&block)
|
20
|
+
hash.each do |key, value|
|
21
|
+
self[key] = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
# MethodReader allows you to access keys of the hash
|
4
|
+
# via method calls. This gives you an OStruct like way
|
5
|
+
# to access your hash's keys. It will recognize keys
|
6
|
+
# either as strings or symbols.
|
7
|
+
#
|
8
|
+
# Note that while nil keys will be returned as nil,
|
9
|
+
# undefined keys will raise NoMethodErrors. Also note that
|
10
|
+
# #respond_to? has been patched to appropriately recognize
|
11
|
+
# key methods.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# class User < Hash
|
15
|
+
# include Hashie::Extensions::MethodReader
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# user = User.new
|
19
|
+
# user['first_name'] = 'Michael'
|
20
|
+
# user.first_name # => 'Michael'
|
21
|
+
#
|
22
|
+
# user[:last_name] = 'Bleigh'
|
23
|
+
# user.last_name # => 'Bleigh'
|
24
|
+
#
|
25
|
+
# user[:birthday] = nil
|
26
|
+
# user.birthday # => nil
|
27
|
+
#
|
28
|
+
# user.not_declared # => NoMethodError
|
29
|
+
module MethodReader
|
30
|
+
def respond_to?(name, include_private = false)
|
31
|
+
return true if key?(name.to_s) || key?(name.to_sym)
|
32
|
+
super
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(name, *args)
|
36
|
+
return self[name.to_s] if key?(name.to_s)
|
37
|
+
return self[name.to_sym] if key?(name.to_sym)
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# MethodWriter gives you #key_name= shortcuts for
|
43
|
+
# writing to your hash. Keys are written as strings,
|
44
|
+
# override #convert_key if you would like to have symbols
|
45
|
+
# or something else.
|
46
|
+
#
|
47
|
+
# Note that MethodWriter also overrides #respond_to such
|
48
|
+
# that any #method_name= will respond appropriately as true.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# class MyHash < Hash
|
52
|
+
# include Hashie::Extensions::MethodWriter
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# h = MyHash.new
|
56
|
+
# h.awesome = 'sauce'
|
57
|
+
# h['awesome'] # => 'sauce'
|
58
|
+
#
|
59
|
+
module MethodWriter
|
60
|
+
def respond_to?(name, include_private = false)
|
61
|
+
return true if name.to_s =~ /=$/
|
62
|
+
super
|
63
|
+
end
|
64
|
+
|
65
|
+
def method_missing(name, *args)
|
66
|
+
if args.size == 1 && name.to_s =~ /(.*)=$/
|
67
|
+
return self[convert_key($1)] = args.first
|
68
|
+
end
|
69
|
+
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def convert_key(key)
|
74
|
+
key.to_s
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# MethodQuery gives you the ability to check for the truthiness
|
79
|
+
# of a key via method calls. Note that it will return false if
|
80
|
+
# the key is set to a non-truthful value, not if the key isn't
|
81
|
+
# set at all. Use #key? for checking if a key has been set.
|
82
|
+
#
|
83
|
+
# MethodQuery will check against both string and symbol names
|
84
|
+
# of the method for existing keys. It also patches #respond_to
|
85
|
+
# to appropriately detect the query methods.
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# class MyHash < Hash
|
89
|
+
# include Hashie::Extensions::MethodQuery
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# h = MyHash.new
|
93
|
+
# h['abc'] = 123
|
94
|
+
# h.abc? # => true
|
95
|
+
# h['def'] = nil
|
96
|
+
# h.def? # => false
|
97
|
+
# h.hji? # => NoMethodError
|
98
|
+
module MethodQuery
|
99
|
+
def respond_to?(name, include_private = false)
|
100
|
+
return true if name.to_s =~ /(.*)\?$/ && (key?($1) || key?($1.to_sym))
|
101
|
+
super
|
102
|
+
end
|
103
|
+
|
104
|
+
def method_missing(name, *args)
|
105
|
+
if args.empty? && name.to_s =~ /(.*)\?$/ && (key?($1) || key?($1.to_sym))
|
106
|
+
return self[$1] || self[$1.to_sym]
|
107
|
+
end
|
108
|
+
|
109
|
+
super
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# A macro module that will automatically include MethodReader,
|
114
|
+
# MethodWriter, and MethodQuery, giving you the ability to read,
|
115
|
+
# write, and query keys in a hash using method call shortcuts.
|
116
|
+
module MethodAccess
|
117
|
+
def self.included(base)
|
118
|
+
[MethodReader, MethodWriter, MethodQuery].each do |mod|
|
119
|
+
base.send :include, mod
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Hashie
|
2
|
+
module Extensions
|
3
|
+
# The Structure extension provides facilities for declaring
|
4
|
+
# properties that a Hash can have. This provides for the
|
5
|
+
# creation of structures that still behave like hashes but
|
6
|
+
# do not allow setting non-allowed keys.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# class RestrictedHash < Hash
|
10
|
+
# include Hashie::Extensions::MergeInitializer
|
11
|
+
# include Hashie::Extensions::Structure
|
12
|
+
#
|
13
|
+
# key :first
|
14
|
+
# key :second, :default => 'foo'
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# h = RestrictedHash.new(:first => 1)
|
18
|
+
# h[:first] # => 1
|
19
|
+
# h[:second] # => 'foo'
|
20
|
+
# h[:third] # => ArgumentError
|
21
|
+
#
|
22
|
+
module Structure
|
23
|
+
def self.included(base)
|
24
|
+
base.extend ClassMethods
|
25
|
+
base.class_eval do
|
26
|
+
@permitted_keys = superclass.permitted_keys if superclass.respond_to?(:permitted_keys)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
def key(key, options = {})
|
32
|
+
(@permitted_keys ||= []) << key
|
33
|
+
|
34
|
+
if options[:default]
|
35
|
+
(@default_values ||= {})[key] = options.delete(:default)
|
36
|
+
end
|
37
|
+
|
38
|
+
permitted_keys
|
39
|
+
end
|
40
|
+
|
41
|
+
def permitted_keys
|
42
|
+
@permitted_keys
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/hashie/hash.rb
CHANGED
@@ -4,15 +4,21 @@ module Hashie
|
|
4
4
|
# A Hashie Hash is simply a Hash that has convenience
|
5
5
|
# functions baked in such as stringify_keys that may
|
6
6
|
# not be available in all libraries.
|
7
|
-
class Hash < Hash
|
8
|
-
include
|
7
|
+
class Hash < ::Hash
|
8
|
+
include HashExtensions
|
9
9
|
|
10
|
-
# Converts a mash back to a hash
|
11
|
-
def to_hash
|
10
|
+
# Converts a mash back to a hash (with stringified keys)
|
11
|
+
def to_hash
|
12
12
|
out = {}
|
13
13
|
keys.each do |k|
|
14
|
-
|
15
|
-
|
14
|
+
if self[k].is_a?(Array)
|
15
|
+
out[k] ||= []
|
16
|
+
self[k].each do |array_object|
|
17
|
+
out[k] << (Hash === array_object ? array_object.to_hash : array_object)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
out[k] = Hash === self[k] ? self[k].to_hash : self[k]
|
21
|
+
end
|
16
22
|
end
|
17
23
|
out
|
18
24
|
end
|