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
43 # If you pass in an existing hash, it will
44 # convert it to a Mash including recursively
45 # descending into arrays and hashes, converting
46 # them as well.
e7bd3ecb » judofyr 2008-07-22 Mash#initialize should acce... 47 def initialize(source_hash = nil, &blk)
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 48 deep_update(source_hash) if source_hash
e7bd3ecb » judofyr 2008-07-22 Mash#initialize should acce... 49 super(&blk)
b7d18541 » mbleigh 2008-04-12 Initial import. 50 end
51
b437e8d9 » judofyr 2008-08-02 Make Mash[] work too 52 class << self; alias [] new; end
53
b7d18541 » mbleigh 2008-04-12 Initial import. 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
e7bd3ecb » judofyr 2008-07-22 Mash#initialize should acce... 70 key ? super : super()
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 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
c566221b » mbleigh 2008-05-06 Fixed issue where Mash init... 98 alias_method :regular_dup, :dup
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 99 # Duplicates the current mash as a new mash.
100 def dup
101 Mash.new(self)
102 end
103
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 104 alias_method :picky_key?, :key?
105 def key?(key)
106 picky_key?(convert_key(key))
b7d18541 » mbleigh 2008-04-12 Initial import. 107 end
108
c566221b » mbleigh 2008-05-06 Fixed issue where Mash init... 109 alias_method :regular_inspect, :inspect
b7d18541 » mbleigh 2008-04-12 Initial import. 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)
c566221b » mbleigh 2008-05-06 Fixed issue where Mash init... 131 other_hash = other_hash.to_hash if other_hash.is_a?(Mash)
132 other_hash = other_hash.stringify_keys
4f8a26b3 » mbleigh 2008-04-29 Mashes do not infinite loop... Comment 133 other_hash.each_pair do |k,v|
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 134 k = convert_key(k)
135 self[k] = self[k].to_mash if self[k].is_a?(Hash) unless self[k].is_a?(Mash)
4f8a26b3 » mbleigh 2008-04-29 Mashes do not infinite loop... Comment 136 if self[k].is_a?(Hash) && other_hash[k].is_a?(Hash)
c566221b » mbleigh 2008-05-06 Fixed issue where Mash init... 137 self[k] = self[k].deep_merge(other_hash[k]).dup
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 138 else
c566221b » mbleigh 2008-05-06 Fixed issue where Mash init... 139 self.send(k + "=", convert_value(other_hash[k],true))
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 140 end
141 end
142 end
143 alias_method :deep_merge!, :deep_update
144
145 # ==== Parameters
146 # other_hash<Hash>::
147 # A hash to update values in the mash with. Keys will be
148 # stringified and Hashes will be converted to Mashes.
149 #
150 # ==== Returns
151 # Mash:: The updated mash.
152 def update(other_hash)
153 other_hash.each_pair do |key, value|
154 if respond_to?(convert_key(key) + "=")
155 self.send(convert_key(key) + "=", convert_value(value))
156 else
157 regular_writer(convert_key(key), convert_value(value))
158 end
159 end
160 self
161 end
162 alias_method :merge!, :update
163
164 # Converts a mash back to a hash (with stringified keys)
165 def to_hash
166 Hash.new(default).merge(self)
167 end
168
b7d18541 » mbleigh 2008-04-12 Initial import. 169 def method_missing(method_name, *args) #:nodoc:
170 if (match = method_name.to_s.match(/(.*)=$/)) && args.size == 1
171 self[match[1]] = args.first
172 elsif (match = method_name.to_s.match(/(.*)\?$/)) && args.size == 0
173 key?(match[1])
8d5f3861 » mbleigh 2008-04-12 Adding bang method support ... 174 elsif (match = method_name.to_s.match(/(.*)!$/)) && args.size == 0
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 175 initializing_reader(match[1])
66eaeec9 » mbleigh 2008-04-19 Updated history, yield defa... 176 elsif key?(method_name)
b7d18541 » mbleigh 2008-04-12 Initial import. 177 self[method_name]
178 elsif match = method_name.to_s.match(/^([a-z][a-z0-9A-Z_]+)$/)
e7bd3ecb » judofyr 2008-07-22 Mash#initialize should acce... 179 default(method_name)
b7d18541 » mbleigh 2008-04-12 Initial import. 180 else
181 super
182 end
183 end
184
8d5f3861 » mbleigh 2008-04-12 Adding bang method support ... 185 protected
b7d18541 » mbleigh 2008-04-12 Initial import. 186
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 187 def convert_key(key) #:nodoc:
188 key.to_s
189 end
190
c566221b » mbleigh 2008-05-06 Fixed issue where Mash init... 191 def convert_value(value, dup=false) #:nodoc:
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 192 case value
193 when Hash
c566221b » mbleigh 2008-05-06 Fixed issue where Mash init... 194 value = value.dup if value.is_a?(Mash) && dup
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 195 value.is_a?(Mash) ? value : value.to_mash
196 when Array
197 value.collect{ |e| convert_value(e) }
198 else
199 value
b7d18541 » mbleigh 2008-04-12 Initial import. 200 end
201 end
76360801 » mbleigh 2008-04-16 [0.0.3] - added aliases to ... 202 end
203
204 class Hash
205 # Returns a new Mash initialized from this Hash.
206 def to_mash
207 mash = Mash.new(self)
208 mash.default = default
209 mash
210 end
63d23cf2 » mbleigh 2008-04-19 - Substantial cleanup of ex... Comment 211
212 # Returns a duplicate of the current hash with
213 # all of the keys converted to strings.
214 def stringify_keys
215 dup.stringify_keys!
216 end
217
218 # Converts all of the keys to strings
219 def stringify_keys!
220 keys.each{|k|
221 v = delete(k)
222 self[k.to_s] = v
223 v.stringify_keys! if v.is_a?(Hash)
224 v.each{|p| p.stringify_keys! if p.is_a?(Hash)} if v.is_a?(Array)
225 }
226 self
227 end
b7d18541 » mbleigh 2008-04-12 Initial import. 228 end