hashie-pre 2.0.0.beta
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/.document +5 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +36 -0
- data/Guardfile +5 -0
- data/LICENSE +20 -0
- data/README.markdown +232 -0
- data/Rakefile +13 -0
- data/VERSION +1 -0
- data/hashie.gemspec +22 -0
- data/lib/hashie.rb +23 -0
- data/lib/hashie/clash.rb +86 -0
- data/lib/hashie/dash.rb +150 -0
- data/lib/hashie/extensions/coercion.rb +101 -0
- data/lib/hashie/extensions/deep_merge.rb +7 -0
- data/lib/hashie/extensions/indifferent_access.rb +110 -0
- data/lib/hashie/extensions/key_conversion.rb +52 -0
- data/lib/hashie/extensions/merge_initializer.rb +24 -0
- data/lib/hashie/extensions/method_access.rb +124 -0
- data/lib/hashie/extensions/structure.rb +47 -0
- data/lib/hashie/hash.rb +31 -0
- data/lib/hashie/hash_extensions.rb +49 -0
- data/lib/hashie/mash.rb +216 -0
- data/lib/hashie/trash.rb +77 -0
- data/lib/hashie/version.rb +3 -0
- data/spec/hashie/clash_spec.rb +42 -0
- data/spec/hashie/dash_spec.rb +215 -0
- data/spec/hashie/extensions/coercion_spec.rb +70 -0
- data/spec/hashie/extensions/indifferent_access_spec.rb +66 -0
- data/spec/hashie/extensions/key_conversion_spec.rb +66 -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 +22 -0
- data/spec/hashie/mash_spec.rb +305 -0
- data/spec/hashie/trash_spec.rb +139 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_helper.rb +12 -0
- metadata +181 -0
@@ -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)
|
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)
|
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)
|
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
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'hashie/hash_extensions'
|
2
|
+
|
3
|
+
module Hashie
|
4
|
+
# A Hashie Hash is simply a Hash that has convenience
|
5
|
+
# functions baked in such as stringify_keys that may
|
6
|
+
# not be available in all libraries.
|
7
|
+
class Hash < ::Hash
|
8
|
+
include Hashie::HashExtensions
|
9
|
+
|
10
|
+
# Converts a mash back to a hash (with stringified keys)
|
11
|
+
def to_hash
|
12
|
+
out = {}
|
13
|
+
keys.each do |k|
|
14
|
+
if self[k].is_a?(Array)
|
15
|
+
out[k] ||= []
|
16
|
+
self[k].each do |array_object|
|
17
|
+
out[k] << (Hashie::Hash === array_object ? array_object.to_hash : array_object)
|
18
|
+
end
|
19
|
+
else
|
20
|
+
out[k] = Hashie::Hash === self[k] ? self[k].to_hash : self[k]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
out
|
24
|
+
end
|
25
|
+
|
26
|
+
# The C geneartor for the json gem doesn't like mashies
|
27
|
+
def to_json(*args)
|
28
|
+
to_hash.to_json(*args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Hashie
|
2
|
+
module HashExtensions
|
3
|
+
def self.included(base)
|
4
|
+
# Don't tread on existing extensions of Hash by
|
5
|
+
# adding methods that are likely to exist.
|
6
|
+
%w(stringify_keys stringify_keys!).each do |hashie_method|
|
7
|
+
base.send :alias_method, hashie_method, "hashie_#{hashie_method}" unless base.instance_methods.include?(hashie_method)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Destructively convert all of the keys of a Hash
|
12
|
+
# to their string representations.
|
13
|
+
def hashie_stringify_keys!
|
14
|
+
self.keys.each do |k|
|
15
|
+
unless String === k
|
16
|
+
self[k.to_s] = self.delete(k)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convert all of the keys of a Hash
|
23
|
+
# to their string representations.
|
24
|
+
def hashie_stringify_keys
|
25
|
+
self.dup.stringify_keys!
|
26
|
+
end
|
27
|
+
|
28
|
+
# Convert this hash into a Mash
|
29
|
+
def to_mash
|
30
|
+
::Hashie::Mash.new(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module PrettyInspect
|
35
|
+
def self.included(base)
|
36
|
+
base.send :alias_method, :hash_inspect, :inspect
|
37
|
+
base.send :alias_method, :inspect, :hashie_inspect
|
38
|
+
end
|
39
|
+
|
40
|
+
def hashie_inspect
|
41
|
+
ret = "#<#{self.class.to_s}"
|
42
|
+
stringify_keys.keys.sort.each do |key|
|
43
|
+
ret << " #{key}=#{self[key].inspect}"
|
44
|
+
end
|
45
|
+
ret << ">"
|
46
|
+
ret
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/hashie/mash.rb
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
require 'hashie/hash'
|
2
|
+
|
3
|
+
module Hashie
|
4
|
+
# Mash allows you to create pseudo-objects that have method-like
|
5
|
+
# accessors for hash keys. This is useful for such implementations
|
6
|
+
# as an API-accessing library that wants to fake robust objects
|
7
|
+
# without the overhead of actually doing so. Think of it as OpenStruct
|
8
|
+
# with some additional goodies.
|
9
|
+
#
|
10
|
+
# A Mash will look at the methods you pass it and perform operations
|
11
|
+
# based on the following rules:
|
12
|
+
#
|
13
|
+
# * No punctuation: Returns the value of the hash for that key, or nil if none exists.
|
14
|
+
# * Assignment (<tt>=</tt>): Sets the attribute of the given method name.
|
15
|
+
# * Existence (<tt>?</tt>): Returns true or false depending on whether that key has been set.
|
16
|
+
# * Bang (<tt>!</tt>): Forces the existence of this key, used for deep Mashes. Think of it as "touch" for mashes.
|
17
|
+
# * Under Bang (<tt>_</tt>): Like Bang, but returns a new Mash rather than creating a key. Used to test existance in deep Mashes.
|
18
|
+
#
|
19
|
+
# == Basic Example
|
20
|
+
#
|
21
|
+
# mash = Mash.new
|
22
|
+
# mash.name? # => false
|
23
|
+
# mash.name = "Bob"
|
24
|
+
# mash.name # => "Bob"
|
25
|
+
# mash.name? # => true
|
26
|
+
#
|
27
|
+
# == Hash Conversion Example
|
28
|
+
#
|
29
|
+
# hash = {:a => {:b => 23, :d => {:e => "abc"}}, :f => [{:g => 44, :h => 29}, 12]}
|
30
|
+
# mash = Mash.new(hash)
|
31
|
+
# mash.a.b # => 23
|
32
|
+
# mash.a.d.e # => "abc"
|
33
|
+
# mash.f.first.g # => 44
|
34
|
+
# mash.f.last # => 12
|
35
|
+
#
|
36
|
+
# == Bang Example
|
37
|
+
#
|
38
|
+
# mash = Mash.new
|
39
|
+
# mash.author # => nil
|
40
|
+
# mash.author! # => <Mash>
|
41
|
+
#
|
42
|
+
# mash = Mash.new
|
43
|
+
# mash.author!.name = "Michael Bleigh"
|
44
|
+
# mash.author # => <Mash name="Michael Bleigh">
|
45
|
+
#
|
46
|
+
# == Under Bang Example
|
47
|
+
#
|
48
|
+
# mash = Mash.new
|
49
|
+
# mash.author # => nil
|
50
|
+
# mash.author_ # => <Mash>
|
51
|
+
# mash.author_.name # => nil
|
52
|
+
#
|
53
|
+
# mash = Mash.new
|
54
|
+
# mash.author_.name = "Michael Bleigh" (assigned to temp object)
|
55
|
+
# mash.author # => <Mash>
|
56
|
+
#
|
57
|
+
class Mash < Hashie::Hash
|
58
|
+
include Hashie::PrettyInspect
|
59
|
+
alias_method :to_s, :inspect
|
60
|
+
|
61
|
+
# If you pass in an existing hash, it will
|
62
|
+
# convert it to a Mash including recursively
|
63
|
+
# descending into arrays and hashes, converting
|
64
|
+
# them as well.
|
65
|
+
def initialize(source_hash = nil, default = nil, &blk)
|
66
|
+
deep_update(source_hash) if source_hash
|
67
|
+
default ? super(default) : super(&blk)
|
68
|
+
end
|
69
|
+
|
70
|
+
class << self; alias [] new; end
|
71
|
+
|
72
|
+
def id #:nodoc:
|
73
|
+
key?("id") ? self["id"] : super
|
74
|
+
end
|
75
|
+
|
76
|
+
def type #:nodoc:
|
77
|
+
key?("type") ? self["type"] : super
|
78
|
+
end
|
79
|
+
|
80
|
+
alias_method :regular_reader, :[]
|
81
|
+
alias_method :regular_writer, :[]=
|
82
|
+
|
83
|
+
# Retrieves an attribute set in the Mash. Will convert
|
84
|
+
# any key passed in to a string before retrieving.
|
85
|
+
def [](key)
|
86
|
+
value = regular_reader(convert_key(key))
|
87
|
+
yield value if block_given?
|
88
|
+
value
|
89
|
+
end
|
90
|
+
|
91
|
+
# Sets an attribute in the Mash. Key will be converted to
|
92
|
+
# a string before it is set, and Hashes will be converted
|
93
|
+
# into Mashes for nesting purposes.
|
94
|
+
def []=(key,value) #:nodoc:
|
95
|
+
regular_writer(convert_key(key), convert_value(value))
|
96
|
+
end
|
97
|
+
|
98
|
+
# This is the bang method reader, it will return a new Mash
|
99
|
+
# if there isn't a value already assigned to the key requested.
|
100
|
+
def initializing_reader(key)
|
101
|
+
ck = convert_key(key)
|
102
|
+
regular_writer(ck, self.class.new) unless key?(ck)
|
103
|
+
regular_reader(ck)
|
104
|
+
end
|
105
|
+
|
106
|
+
# This is the under bang method reader, it will return a temporary new Mash
|
107
|
+
# if there isn't a value already assigned to the key requested.
|
108
|
+
def underbang_reader(key)
|
109
|
+
ck = convert_key(key)
|
110
|
+
if key?(ck)
|
111
|
+
regular_reader(ck)
|
112
|
+
else
|
113
|
+
self.class.new
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def delete(key)
|
118
|
+
super(convert_key(key))
|
119
|
+
end
|
120
|
+
|
121
|
+
alias_method :regular_dup, :dup
|
122
|
+
# Duplicates the current mash as a new mash.
|
123
|
+
def dup
|
124
|
+
self.class.new(self, self.default)
|
125
|
+
end
|
126
|
+
|
127
|
+
def key?(key)
|
128
|
+
super(convert_key(key))
|
129
|
+
end
|
130
|
+
alias_method :has_key?, :key?
|
131
|
+
alias_method :include?, :key?
|
132
|
+
alias_method :member?, :key?
|
133
|
+
|
134
|
+
# Performs a deep_update on a duplicate of the
|
135
|
+
# current mash.
|
136
|
+
def deep_merge(other_hash)
|
137
|
+
dup.deep_update(other_hash)
|
138
|
+
end
|
139
|
+
alias_method :merge, :deep_merge
|
140
|
+
|
141
|
+
# Recursively merges this mash with the passed
|
142
|
+
# in hash, merging each hash in the hierarchy.
|
143
|
+
def deep_update(other_hash)
|
144
|
+
other_hash.each_pair do |k,v|
|
145
|
+
key = convert_key(k)
|
146
|
+
if regular_reader(key).is_a?(Mash) and v.is_a?(::Hash)
|
147
|
+
regular_reader(key).deep_update(v)
|
148
|
+
else
|
149
|
+
regular_writer(key, convert_value(v, true))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
self
|
153
|
+
end
|
154
|
+
alias_method :deep_merge!, :deep_update
|
155
|
+
alias_method :update, :deep_update
|
156
|
+
alias_method :merge!, :update
|
157
|
+
|
158
|
+
# Performs a shallow_update on a duplicate of the current mash
|
159
|
+
def shallow_merge(other_hash)
|
160
|
+
dup.shallow_update(other_hash)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Merges (non-recursively) the hash from the argument,
|
164
|
+
# changing the receiving hash
|
165
|
+
def shallow_update(other_hash)
|
166
|
+
other_hash.each_pair do |k,v|
|
167
|
+
regular_writer(convert_key(k), convert_value(v, true))
|
168
|
+
end
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
# Will return true if the Mash has had a key
|
173
|
+
# set in addition to normal respond_to? functionality.
|
174
|
+
def respond_to?(method_name, include_private=false)
|
175
|
+
return true if key?(method_name)
|
176
|
+
super
|
177
|
+
end
|
178
|
+
|
179
|
+
def method_missing(method_name, *args, &blk)
|
180
|
+
return self.[](method_name, &blk) if key?(method_name)
|
181
|
+
match = method_name.to_s.match(/(.*?)([?=!_]?)$/)
|
182
|
+
case match[2]
|
183
|
+
when "="
|
184
|
+
self[match[1]] = args.first
|
185
|
+
when "?"
|
186
|
+
!!self[match[1]]
|
187
|
+
when "!"
|
188
|
+
initializing_reader(match[1])
|
189
|
+
when "_"
|
190
|
+
underbang_reader(match[1])
|
191
|
+
else
|
192
|
+
default(method_name, *args, &blk)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
protected
|
197
|
+
|
198
|
+
def convert_key(key) #:nodoc:
|
199
|
+
key.to_s
|
200
|
+
end
|
201
|
+
|
202
|
+
def convert_value(val, duping=false) #:nodoc:
|
203
|
+
case val
|
204
|
+
when self.class
|
205
|
+
val.dup
|
206
|
+
when ::Hash
|
207
|
+
val = val.dup if duping
|
208
|
+
self.class.new(val)
|
209
|
+
when Array
|
210
|
+
val.collect{ |e| convert_value(e) }
|
211
|
+
else
|
212
|
+
val
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|