peterpunk-mhash 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,30 @@
1
+ === 0.0.9 / 2008-09-01
2
+
3
+ * Rename the class to Mhash to avoid Merb Mash class conflict.
4
+
5
+ === 0.0.5 / 2008-04-29
6
+
7
+ * [bugfix] Mashes do not infinite loop when initialized with another Mash.
8
+
9
+ === 0.0.4 / 2008-04-25
10
+
11
+ * Setting up for GitHub gem hosting instead of Rubyforge.
12
+
13
+ === 0.0.3 / 2008-04-19
14
+
15
+ * [] no longer defaults to a new Mash, will return nil if
16
+ * Attribute-esque method names will yield the default value if not set
17
+ * Hash extended with #to_mash and #stringify_keys
18
+ * Added #dup and #deep_merge
19
+ * Aliased the default Hash methods so they are still accessible
20
+ * Cleaned up the recursive conversion process
21
+
22
+ === 0.0.2 / 2008-04-12
23
+
24
+ * Added bang(!) method support
25
+ * No longer automatically multi-level assigning
26
+ * Hash conversion now calls methods instead of []= to allow for overrides
27
+
28
+ === 0.0.1 / 2008-04-12
29
+
30
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/mhash.rb
6
+ spec/mhash_spec.rb
7
+ spec/spec_helper.rb
data/README.txt ADDED
@@ -0,0 +1,69 @@
1
+ = Mhash (Mocking Hash)
2
+
3
+ http://github.com/mbleigh/mhash
4
+
5
+ == DESCRIPTION:
6
+
7
+ Mhash is an extended Hash that gives simple pseudo-object functionality
8
+ that can be built from hashes and easily extended. It is designed to
9
+ be used in RESTful API libraries to provide easy object-like access
10
+ to JSON and XML parsed hashes.
11
+
12
+ == SYNOPSIS:
13
+
14
+ mhash = Mhash.new
15
+ mhash.name? # => false
16
+ mhash.name # => nil
17
+ mhash.name = "My Mhash"
18
+ mhash.name # => "My Mhash"
19
+ mhash.name? # => true
20
+ mhash.inspect # => <Mhash name="My Mhash">
21
+
22
+ mhash = Mhash.new
23
+ # use bang methods for multi-level assignment
24
+ mhash.author!.name = "Michael Bleigh"
25
+ mhash.author # => <Mhash name="Michael Bleigh">
26
+
27
+ == INSTALL:
28
+
29
+ Gem:
30
+
31
+ Mhash is hosted on the GitHub gem repository, so if you haven't already:
32
+
33
+ gem sources -a http://gems.github.com/
34
+ sudo gem install mbleigh-mhash
35
+
36
+ Git:
37
+
38
+ git clone git://github.com/mbleigh/mhash.git
39
+
40
+ == RESOURCES
41
+
42
+ If you encounter any problems or have ideas for new features
43
+ please report them at the Lighthouse project for Mhash:
44
+
45
+ http://mbleigh.lighthouseapp.com/projects/10112-mash
46
+
47
+ == LICENSE:
48
+
49
+ Copyright (c) 2008 Michael Bleigh (http://mbleigh.com/)
50
+ and Intridea Inc. (http://intridea.com/), released under the MIT license
51
+
52
+ Permission is hereby granted, free of charge, to any person obtaining
53
+ a copy of this software and associated documentation files (the
54
+ 'Software'), to deal in the Software without restriction, including
55
+ without limitation the rights to use, copy, modify, merge, publish,
56
+ distribute, sublicense, and/or sell copies of the Software, and to
57
+ permit persons to whom the Software is furnished to do so, subject to
58
+ the following conditions:
59
+
60
+ The above copyright notice and this permission notice shall be
61
+ included in all copies or substantial portions of the Software.
62
+
63
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
64
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
65
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
66
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
67
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
68
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
69
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require './lib/mhash.rb'
3
+ require 'spec/rake/spectask'
4
+
5
+ desc "Run specs."
6
+ Spec::Rake::SpecTask.new("spec") do |t|
7
+ t.spec_files = "spec/*_spec.rb"
8
+ end
data/lib/mhash.rb ADDED
@@ -0,0 +1,228 @@
1
+ # Mhash 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
+ #
7
+ # A Mhash 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 Mhashes. Think of it as "touch" for mhashes.
14
+ #
15
+ # == Basic Example
16
+ #
17
+ # mhash = Mhash.new
18
+ # mhash.name? # => false
19
+ # mhash.name = "Bob"
20
+ # mhash.name # => "Bob"
21
+ # mhash.name? # => true
22
+ #
23
+ # == Hash Conversion Example
24
+ #
25
+ # hash = {:a => {:b => 23, :d => {:e => "abc"}}, :f => [{:g => 44, :h => 29}, 12]}
26
+ # mhash = Mhash.new(hash)
27
+ # mhash.a.b # => 23
28
+ # mhash.a.d.e # => "abc"
29
+ # mhash.f.first.g # => 44
30
+ # mhash.f.last # => 12
31
+ #
32
+ # == Bang Example
33
+ #
34
+ # mhash = Mhash.new
35
+ # mhash.author # => nil
36
+ # mhash.author! # => <Mhash>
37
+ #
38
+ # mhash = Mhash.new
39
+ # mhash.author!.name = "Michael Bleigh"
40
+ # mhash.author # => <Mhash name="Michael Bleigh">
41
+ #
42
+ class Mhash < Hash
43
+ # If you pass in an existing hash, it will
44
+ # convert it to a Mhash including recursively
45
+ # descending into arrays and hashes, converting
46
+ # them as well.
47
+ def initialize(source_hash = nil, &blk)
48
+ deep_update(source_hash) if source_hash
49
+ super(&blk)
50
+ end
51
+
52
+ class << self; alias [] new; end
53
+
54
+ def id #:nodoc:
55
+ self["id"] ? self["id"] : super
56
+ end
57
+
58
+ # Borrowed from Merb's Mhash object.
59
+ #
60
+ # ==== Parameters
61
+ # key<Object>:: The default value for the mhash. Defaults to nil.
62
+ #
63
+ # ==== Alternatives
64
+ # If key is a Symbol and it is a key in the mhash, then the default value will
65
+ # be set to the value matching the key.
66
+ def default(key = nil)
67
+ if key.is_a?(Symbol) && key?(key)
68
+ self[key]
69
+ else
70
+ key ? super : super()
71
+ end
72
+ end
73
+
74
+ alias_method :regular_reader, :[]
75
+ alias_method :regular_writer, :[]=
76
+
77
+ # Retrieves an attribute set in the Mhash. Will convert
78
+ # any key passed in to a string before retrieving.
79
+ def [](key)
80
+ key = convert_key(key)
81
+ regular_reader(key)
82
+ end
83
+
84
+ # Sets an attribute in the Mhash. Key will be converted to
85
+ # a string before it is set.
86
+ def []=(key,value) #:nodoc:
87
+ key = convert_key(key)
88
+ regular_writer(key,convert_value(value))
89
+ end
90
+
91
+ # This is the bang method reader, it will return a new Mhash
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] = Mhash.new
96
+ end
97
+
98
+ alias_method :regular_dup, :dup
99
+ # Duplicates the current mhash as a new mhash.
100
+ def dup
101
+ Mhash.new(self)
102
+ end
103
+
104
+ alias_method :picky_key?, :key?
105
+ def key?(key)
106
+ picky_key?(convert_key(key))
107
+ end
108
+
109
+ alias_method :regular_inspect, :inspect
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
120
+ alias_method :to_s, :inspect
121
+
122
+ # Performs a deep_update on a duplicate of the
123
+ # current mhash.
124
+ def deep_merge(other_hash)
125
+ dup.deep_merge!(other_hash)
126
+ end
127
+
128
+ # Recursively merges this mhash with the passed
129
+ # in hash, merging each hash in the hierarchy.
130
+ def deep_update(other_hash)
131
+ other_hash = other_hash.to_hash if other_hash.is_a?(Mhash)
132
+ other_hash = other_hash.stringify_keys
133
+ other_hash.each_pair do |k,v|
134
+ k = convert_key(k)
135
+ self[k] = self[k].to_mhash if self[k].is_a?(Hash) unless self[k].is_a?(Mhash)
136
+ if self[k].is_a?(Hash) && other_hash[k].is_a?(Hash)
137
+ self[k] = self[k].deep_merge(other_hash[k]).dup
138
+ else
139
+ self.send(k + "=", convert_value(other_hash[k],true))
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 mhash with. Keys will be
148
+ # stringified and Hashes will be converted to Mhashes.
149
+ #
150
+ # ==== Returns
151
+ # Mhash:: The updated mhash.
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 mhash back to a hash (with stringified keys)
165
+ def to_hash
166
+ Hash.new(default).merge(self)
167
+ end
168
+
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])
174
+ elsif (match = method_name.to_s.match(/(.*)!$/)) && args.size == 0
175
+ initializing_reader(match[1])
176
+ elsif key?(method_name)
177
+ self[method_name]
178
+ elsif match = method_name.to_s.match(/^([a-z][a-z0-9A-Z_]+)$/)
179
+ default(method_name)
180
+ else
181
+ super
182
+ end
183
+ end
184
+
185
+ protected
186
+
187
+ def convert_key(key) #:nodoc:
188
+ key.to_s
189
+ end
190
+
191
+ def convert_value(value, dup=false) #:nodoc:
192
+ case value
193
+ when Hash
194
+ value = value.dup if value.is_a?(Mhash) && dup
195
+ value.is_a?(Mhash) ? value : value.to_mhash
196
+ when Array
197
+ value.collect{ |e| convert_value(e) }
198
+ else
199
+ value
200
+ end
201
+ end
202
+ end
203
+
204
+ class Hash
205
+ # Returns a new Mhash initialized from this Hash.
206
+ def to_mhash
207
+ mhash = Mhash.new(self)
208
+ mhash.default = default
209
+ mhash
210
+ end
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
228
+ end
data/mhash.gemspec ADDED
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "mhash"
3
+ s.version = "0.0.8"
4
+ s.date = "2008-07-22"
5
+ s.summary = "An extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended"
6
+ s.email = "michael@intridea.com"
7
+ s.homepage = "http://github.com/peterpunk/mhash"
8
+ s.summary = "Mhash is an extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended"
9
+ s.has_rdoc = true
10
+ s.authors = ["Michael Bleigh"]
11
+ s.files = ["History.txt", "Manifest.txt", "README.txt", "Rakefile", "mhash.gemspec", "lib/mhash.rb", "spec/mhash_spec.rb","spec/spec_helper.rb"]
12
+ s.rdoc_options = ["--main", "README.txt"]
13
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.txt"]
14
+ end
@@ -0,0 +1,139 @@
1
+ require File.join(File.dirname(__FILE__),"..","lib","mhash")
2
+ require File.join(File.dirname(__FILE__),"spec_helper")
3
+
4
+ describe Mhash do
5
+ before(:each) do
6
+ @mhash = Mhash.new
7
+ end
8
+
9
+ it "should inherit from hash" do
10
+ @mhash.is_a?(Hash).should be_true
11
+ end
12
+
13
+ it "should be able to set hash values through method= calls" do
14
+ @mhash.test = "abc"
15
+ @mhash["test"].should == "abc"
16
+ end
17
+
18
+ it "should be able to retrieve set values through method calls" do
19
+ @mhash["test"] = "abc"
20
+ @mhash.test.should == "abc"
21
+ end
22
+
23
+ it "should test for already set values when passed a ? method" do
24
+ @mhash.test?.should be_false
25
+ @mhash.test = "abc"
26
+ @mhash.test?.should be_true
27
+ end
28
+
29
+ it "should make all [] and []= into strings for consistency" do
30
+ @mhash["abc"] = 123
31
+ @mhash.key?('abc').should be_true
32
+ @mhash["abc"].should == 123
33
+ end
34
+
35
+ it "should have a to_s that is identical to its inspect" do
36
+ @mhash.abc = 123
37
+ @mhash.to_s.should == @mhash.inspect
38
+ end
39
+
40
+ it "should return nil instead of raising an error for attribute-esque method calls" do
41
+ @mhash.abc.should be_nil
42
+ end
43
+
44
+ it "should return a Mash when passed a bang method to a non-existenct key" do
45
+ @mhash.abc!.is_a?(Mhash).should be_true
46
+ end
47
+
48
+ it "should return the existing value when passed a bang method for an existing key" do
49
+ @mhash.name = "Bob"
50
+ @mhash.name!.should == "Bob"
51
+ end
52
+
53
+ it "#initializing_reader should return a Mhash when passed a non-existent key" do
54
+ @mhash.initializing_reader(:abc).is_a?(Mhash).should be_true
55
+ end
56
+
57
+ it "should allow for multi-level assignment through bang methods" do
58
+ @mhash.author!.name = "Michael Bleigh"
59
+ @mhash.author.should == Mhash.new(:name => "Michael Bleigh")
60
+ @mhash.author!.website!.url = "http://www.mbleigh.com/"
61
+ @mhash.author.website.should == Mhash.new(:url => "http://www.mbleigh.com/")
62
+ end
63
+
64
+ it "#deep_update should recursively mhash mhashes and hashes together" do
65
+ @mhash.first_name = "Michael"
66
+ @mhash.last_name = "Bleigh"
67
+ @mhash.details = {:email => "michael@asf.com"}.to_mhash
68
+ @mhash.deep_update({:details => {:email => "michael@intridea.com"}})
69
+ @mhash.details.email.should == "michael@intridea.com"
70
+ end
71
+
72
+ it "should convert hash assignments into mhashes" do
73
+ @mhash.details = {:email => 'randy@asf.com', :address => {:state => 'TX'} }
74
+ @mhash.details.email.should == 'randy@asf.com'
75
+ @mhash.details.address.state.should == 'TX'
76
+ end
77
+
78
+ context "#initialize" do
79
+ it "should convert an existing hash to a Mhash" do
80
+ converted = Mhash.new({:abc => 123, :name => "Bob"})
81
+ converted.abc.should == 123
82
+ converted.name.should == "Bob"
83
+ end
84
+
85
+ it "should convert hashes recursively into mhashes" do
86
+ converted = Mhash.new({:a => {:b => 1, :c => {:d => 23}}})
87
+ converted.a.is_a?(Mhash).should be_true
88
+ converted.a.b.should == 1
89
+ converted.a.c.d.should == 23
90
+ end
91
+
92
+ it "should convert hashes in arrays into mhashes" do
93
+ converted = Mhash.new({:a => [{:b => 12}, 23]})
94
+ converted.a.first.b.should == 12
95
+ converted.a.last.should == 23
96
+ end
97
+
98
+ it "should convert an existing Mhash into a Mhash" do
99
+ initial = Mhash.new(:name => 'randy', :address => {:state => 'TX'})
100
+ copy = Mhash.new(initial)
101
+ initial.name.should == copy.name
102
+ initial.object_id.should_not == copy.object_id
103
+ copy.address.state.should == 'TX'
104
+ copy.address.state = 'MI'
105
+ initial.address.state.should == 'TX'
106
+ copy.address.object_id.should_not == initial.address.object_id
107
+ end
108
+
109
+ it "should accept a default block" do
110
+ initial = Mhash.new { |h,i| h[i] = []}
111
+ initial.default_proc.should_not be_nil
112
+ initial.default.should be_nil
113
+ initial.test.should == []
114
+ initial.test?.should be_true
115
+ end
116
+ end
117
+ end
118
+
119
+ describe Hash do
120
+ it "should be convertible to a Mhash" do
121
+ mhash = {:some => "hash"}.to_mhash
122
+ mhash.is_a?(Mhash).should be_true
123
+ mhash.some.should == "hash"
124
+ end
125
+
126
+ it "#stringify_keys! should turn all keys into strings" do
127
+ hash = {:a => "hey", 123 => "bob"}
128
+ hash.stringify_keys!
129
+ hash.should == {"a" => "hey", "123" => "bob"}
130
+ end
131
+
132
+ it "#stringify_keys should return a hash with stringified keys" do
133
+ hash = {:a => "hey", 123 => "bob"}
134
+ stringified_hash = hash.stringify_keys
135
+ hash.should == {:a => "hey", 123 => "bob"}
136
+ stringified_hash.should == {"a" => "hey", "123" => "bob"}
137
+ end
138
+
139
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__),"..","lib","mhash")
metadata ADDED
@@ -0,0 +1,63 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: peterpunk-mhash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8
5
+ platform: ruby
6
+ authors:
7
+ - Michael Bleigh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-07-22 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: michael@intridea.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - History.txt
24
+ - Manifest.txt
25
+ - README.txt
26
+ files:
27
+ - History.txt
28
+ - Manifest.txt
29
+ - README.txt
30
+ - Rakefile
31
+ - mhash.gemspec
32
+ - lib/mhash.rb
33
+ - spec/mhash_spec.rb
34
+ - spec/spec_helper.rb
35
+ has_rdoc: true
36
+ homepage: http://github.com/peterpunk/mhash
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --main
40
+ - README.txt
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.2.0
59
+ signing_key:
60
+ specification_version: 2
61
+ summary: Mhash is an extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended
62
+ test_files: []
63
+