mbleigh / mash

Mash is a Hash with the ability to read, write, and test for the presence of arbitrary attributes using method calls.

This URL has Read+Write access

mash / lib / mash.rb
b7d18541 » mbleigh 2008-04-12 Initial import. 1 # Mash allows you to create pseudo-objects that have method-like
2 # accessors for hash keys. This is useful for such implementations
3 # as an API-accessing library that wants to fake robust objects
4 # without the overhead of actually doing so. Think of it as OpenStruct
5 # with some additional goodies.
6 #
8d5f3861 » mbleigh 2008-04-12 Adding bang method support ... 7 # A Mash will look at the methods you pass it and perform operations
8 # based on the following rules:
9 #
10 # * No punctuation: Returns the value of the hash for that key, or nil if none exists.
11 # * Assignment (<tt>=</tt>): Sets the attribute of the given method name.
12 # * Existence (<tt>?</tt>): Returns true or false depending on whether that key has been set.
13 # * Bang (<tt>!</tt>): Forces the existence of this key, used for deep Mashes. Think of it as "touch" for mashes.
14 #
b7d18541 » mbleigh 2008-04-12 Initial import. 15 # == Basic Example
16 #
17 # mash = Mash.new
18 # mash.name? # => false
19 # mash.name = "Bob"
20 # mash.name # => "Bob"
21 # mash.name? # => true
22 #
23 # == Hash Conversion Example
24 #
25 # hash = {:a => {:b => 23, :d => {:e => "abc"}}, :f => [{:g => 44, :h => 29}, 12]}
26 # mash = Mash.new(hash)
27 # mash.a.b # => 23
28 # mash.a.d.e # => "abc"
29 # mash.f.first.g # => 44
30 # mash.f.last # => 12
31 #
8d5f3861 » mbleigh 2008-04-12 Adding bang method support ... 32 # == Bang Example
33 #
34 # mash = Mash.new
35 # mash.author # => nil
36 # mash.author! # => <Mash>
37 #
38 # mash = Mash.new
39 # mash.author!.name = "Michael Bleigh"
40 # mash.author # => <Mash name="Michael Bleigh">
41 #
b7d18541 » mbleigh 2008-04-12 Initial import. 42 class Mash < Hash
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 43 VERSION = '0.0.3'
b7d18541 » mbleigh 2008-04-12 Initial import. 44
45 # If you pass in an existing hash, it will
46 # convert it to a Mash including recursively
47 # descending into arrays and hashes, converting
48 # them as well.
49 def initialize(source_hash = nil)
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 50 deep_update(source_hash) if source_hash
b7d18541 » mbleigh 2008-04-12 Initial import. 51 super(nil)
52 end
53
54 def id #:nodoc:
55 self["id"] ? self["id"] : super
56 end
57
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 58 # Borrowed from Merb's Mash object.
59 #
60 # ==== Parameters
61 # key<Object>:: The default value for the mash. Defaults to nil.
62 #
63 # ==== Alternatives
64 # If key is a Symbol and it is a key in the mash, then the default value will
65 # be set to the value matching the key.
66 def default(key = nil)
66eaeec9 » mbleigh 2008-04-19 Updated history, yield defa... 67 if key.is_a?(Symbol) && key?(key)
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 68 self[key]
69 else
70 super
71 end
72 end
73
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 74 alias_method :regular_reader, :[]
75 alias_method :regular_writer, :[]=
76
77 # Retrieves an attribute set in the Mash. Will convert
78 # any key passed in to a string before retrieving.
79 def [](key)
80 key = convert_key(key)
81 regular_reader(key)
b7d18541 » mbleigh 2008-04-12 Initial import. 82 end
83
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 84 # Sets an attribute in the Mash. Key will be converted to
85 # a string before it is set.
b7d18541 » mbleigh 2008-04-12 Initial import. 86 def []=(key,value) #:nodoc:
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 87 key = convert_key(key)
88 regular_writer(key,convert_value(value))
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 89 end
90
91 # This is the bang method reader, it will return a new Mash
92 # if there isn't a value already assigned to the key requested.
93 def initializing_reader(key)
94 return self[key] if key?(key)
95 self[key] = Mash.new
96 end
97
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 98 # Duplicates the current mash as a new mash.
99 def dup
100 Mash.new(self)
101 end
102
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 103 alias_method :regular_inspect, :inspect
104
105 alias_method :picky_key?, :key?
106 def key?(key)
107 picky_key?(convert_key(key))
b7d18541 » mbleigh 2008-04-12 Initial import. 108 end
109
110 # Prints out a pretty object-like string of the
111 # defined attributes.
112 def inspect
113 ret = "<#{self.class.to_s}"
114 keys.sort.each do |key|
115 ret << " #{key}=#{self[key].inspect}"
116 end
117 ret << ">"
118 ret
119 end
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 120 alias_method :to_s, :inspect
b7d18541 » mbleigh 2008-04-12 Initial import. 121
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 122 # Performs a deep_update on a duplicate of the
123 # current mash.
124 def deep_merge(other_hash)
125 dup.deep_merge!(other_hash)
126 end
127
128 # Recursively merges this mash with the passed
129 # in hash, merging each hash in the hierarchy.
130 def deep_update(other_hash)
131 stringified_hash = other_hash.stringify_keys
132 stringified_hash.each_pair do |k,v|
133 k = convert_key(k)
134 self[k] = self[k].to_mash if self[k].is_a?(Hash) unless self[k].is_a?(Mash)
135 if self[k].is_a?(Hash) && stringified_hash[k].is_a?(Hash)
136 self[k].deep_merge!(stringified_hash[k])
137 else
138 self.send(k + "=", convert_value(stringified_hash[k]))
139 end
140 end
141 end
142 alias_method :deep_merge!, :deep_update
143
144 # ==== Parameters
145 # other_hash<Hash>::
146 # A hash to update values in the mash with. Keys will be
147 # stringified and Hashes will be converted to Mashes.
148 #
149 # ==== Returns
150 # Mash:: The updated mash.
151 def update(other_hash)
152 other_hash.each_pair do |key, value|
153 if respond_to?(convert_key(key) + "=")
154 self.send(convert_key(key) + "=", convert_value(value))
155 else
156 regular_writer(convert_key(key), convert_value(value))
157 end
158 end
159 self
160 end
161 alias_method :merge!, :update
162
163 # Converts a mash back to a hash (with stringified keys)
164 def to_hash
165 Hash.new(default).merge(self)
166 end
167
b7d18541 » mbleigh 2008-04-12 Initial import. 168 def method_missing(method_name, *args) #:nodoc:
169 if (match = method_name.to_s.match(/(.*)=$/)) && args.size == 1
170 self[match[1]] = args.first
171 elsif (match = method_name.to_s.match(/(.*)\?$/)) && args.size == 0
172 key?(match[1])
8d5f3861 » mbleigh 2008-04-12 Adding bang method support ... 173 elsif (match = method_name.to_s.match(/(.*)!$/)) && args.size == 0
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 174 initializing_reader(match[1])
66eaeec9 » mbleigh 2008-04-19 Updated history, yield defa... 175 elsif key?(method_name)
b7d18541 » mbleigh 2008-04-12 Initial import. 176 self[method_name]
177 elsif match = method_name.to_s.match(/^([a-z][a-z0-9A-Z_]+)$/)
66eaeec9 » mbleigh 2008-04-19 Updated history, yield defa... 178 default
b7d18541 » mbleigh 2008-04-12 Initial import. 179 else
180 super
181 end
182 end
183
8d5f3861 » mbleigh 2008-04-12 Adding bang method support ... 184 protected
b7d18541 » mbleigh 2008-04-12 Initial import. 185
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 186 def convert_key(key) #:nodoc:
187 key.to_s
188 end
189
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 190 def convert_value(value) #:nodoc:
191 case value
192 when Hash
193 value.is_a?(Mash) ? value : value.to_mash
194 when Array
195 value.collect{ |e| convert_value(e) }
196 else
197 value
b7d18541 » mbleigh 2008-04-12 Initial import. 198 end
199 end
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 200 end
201
202 class Hash
203 # Returns a new Mash initialized from this Hash.
204 def to_mash
205 mash = Mash.new(self)
206 mash.default = default
207 mash
208 end
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 209
210 # Returns a duplicate of the current hash with
211 # all of the keys converted to strings.
212 def stringify_keys
213 dup.stringify_keys!
214 end
215
216 # Converts all of the keys to strings
217 def stringify_keys!
218 keys.each{|k|
219 v = delete(k)
220 self[k.to_s] = v
221 v.stringify_keys! if v.is_a?(Hash)
222 v.each{|p| p.stringify_keys! if p.is_a?(Hash)} if v.is_a?(Array)
223 }
224 self
225 end
b7d18541 » mbleigh 2008-04-12 Initial import. 226 end