sundbp-extlib 0.9.14

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.
Files changed (72) hide show
  1. data/.autotest +21 -0
  2. data/.document +5 -0
  3. data/.gitignore +22 -0
  4. data/LICENSE +47 -0
  5. data/README.rdoc +17 -0
  6. data/Rakefile +28 -0
  7. data/VERSION +1 -0
  8. data/extlib.gemspec +146 -0
  9. data/lib/extlib.rb +50 -0
  10. data/lib/extlib/array.rb +36 -0
  11. data/lib/extlib/assertions.rb +8 -0
  12. data/lib/extlib/blank.rb +89 -0
  13. data/lib/extlib/boolean.rb +11 -0
  14. data/lib/extlib/byte_array.rb +6 -0
  15. data/lib/extlib/class.rb +177 -0
  16. data/lib/extlib/datetime.rb +29 -0
  17. data/lib/extlib/dictionary.rb +433 -0
  18. data/lib/extlib/hash.rb +442 -0
  19. data/lib/extlib/hook.rb +403 -0
  20. data/lib/extlib/inflection.rb +440 -0
  21. data/lib/extlib/lazy_array.rb +451 -0
  22. data/lib/extlib/lazy_module.rb +18 -0
  23. data/lib/extlib/logger.rb +198 -0
  24. data/lib/extlib/mash.rb +155 -0
  25. data/lib/extlib/module.rb +47 -0
  26. data/lib/extlib/nil.rb +5 -0
  27. data/lib/extlib/numeric.rb +5 -0
  28. data/lib/extlib/object.rb +175 -0
  29. data/lib/extlib/object_space.rb +13 -0
  30. data/lib/extlib/pathname.rb +20 -0
  31. data/lib/extlib/pooling.rb +235 -0
  32. data/lib/extlib/rubygems.rb +38 -0
  33. data/lib/extlib/simple_set.rb +66 -0
  34. data/lib/extlib/string.rb +176 -0
  35. data/lib/extlib/struct.rb +17 -0
  36. data/lib/extlib/symbol.rb +21 -0
  37. data/lib/extlib/time.rb +43 -0
  38. data/lib/extlib/virtual_file.rb +10 -0
  39. data/spec/array_spec.rb +39 -0
  40. data/spec/blank_spec.rb +85 -0
  41. data/spec/byte_array_spec.rb +7 -0
  42. data/spec/class_spec.rb +157 -0
  43. data/spec/datetime_spec.rb +22 -0
  44. data/spec/hash_spec.rb +537 -0
  45. data/spec/hook_spec.rb +1234 -0
  46. data/spec/inflection/plural_spec.rb +564 -0
  47. data/spec/inflection/singular_spec.rb +497 -0
  48. data/spec/inflection_extras_spec.rb +110 -0
  49. data/spec/lazy_array_spec.rb +1957 -0
  50. data/spec/lazy_module_spec.rb +38 -0
  51. data/spec/mash_spec.rb +311 -0
  52. data/spec/module_spec.rb +70 -0
  53. data/spec/object_space_spec.rb +9 -0
  54. data/spec/object_spec.rb +114 -0
  55. data/spec/pooling_spec.rb +511 -0
  56. data/spec/rcov.opts +6 -0
  57. data/spec/simple_set_spec.rb +57 -0
  58. data/spec/spec.opts +4 -0
  59. data/spec/spec_helper.rb +10 -0
  60. data/spec/string_spec.rb +221 -0
  61. data/spec/struct_spec.rb +12 -0
  62. data/spec/symbol_spec.rb +8 -0
  63. data/spec/time_spec.rb +29 -0
  64. data/spec/try_call_spec.rb +73 -0
  65. data/spec/try_dup_spec.rb +45 -0
  66. data/spec/virtual_file_spec.rb +21 -0
  67. data/tasks/ci.rake +1 -0
  68. data/tasks/metrics.rake +36 -0
  69. data/tasks/spec.rake +25 -0
  70. data/tasks/yard.rake +9 -0
  71. data/tasks/yardstick.rake +19 -0
  72. metadata +180 -0
@@ -0,0 +1,17 @@
1
+ class Struct
2
+ ##
3
+ # Get a hash with names and values of all instance variables.
4
+ #
5
+ # class Foo < Struct.new(:name, :age, :gender); end
6
+ # f = Foo.new("Jill", 50, :female)
7
+ # f.attributes #=> {:name => "Jill", :age => 50, :gender => :female}
8
+ #
9
+ # @return [Hash] Hash of instance variables in receiver, keyed by ivar name
10
+ #
11
+ # @api public
12
+ def attributes
13
+ h = {}
14
+ each_pair { |k,v| h[k] = v }
15
+ h
16
+ end
17
+ end # class Struct
@@ -0,0 +1,21 @@
1
+ class Symbol
2
+
3
+ def try_dup
4
+ self
5
+ end
6
+
7
+ ##
8
+ # Join with _o_ as a file path
9
+ #
10
+ # :merb/"core_ext" #=> "merb/core_ext"
11
+ # :merb / :core_ext / :string #=> "merb/core_ext/string"
12
+ #
13
+ # @param [#to_s] o The path component(s) to append.
14
+ #
15
+ # @return [String] The receiver (as path string), concatenated with _o_.
16
+ #
17
+ # @api public
18
+ def /(o)
19
+ File.join(self.to_s, o.to_s)
20
+ end
21
+ end
@@ -0,0 +1,43 @@
1
+ require "date"
2
+
3
+ class Time
4
+
5
+ ##
6
+ # Convert to ISO 8601 representation
7
+ #
8
+ # Time.now.to_json #=> "\"2008-03-28T17:54:20-05:00\""
9
+ #
10
+ # @return [String]
11
+ # ISO 8601 compatible representation of the Time object.
12
+ #
13
+ # @api public
14
+ def to_json(*)
15
+ self.xmlschema.to_json
16
+ end
17
+
18
+ ##
19
+ # Return receiver (for DateTime/Time conversion protocol).
20
+ #
21
+ # Time.now.to_time #=> Wed Nov 19 20:08:28 -0800 2008
22
+ #
23
+ # @return [Time] Receiver
24
+ #
25
+ # @api public
26
+ remove_method :to_time if instance_methods(false).any? { |m| m.to_sym == :to_time }
27
+ def to_time
28
+ self
29
+ end
30
+
31
+ ##
32
+ # Convert to DateTime (for DateTime/Time conversion protocol).
33
+ #
34
+ # Time.now.to_datetime #=> #<DateTime: 106046956823/43200,-1/3,2299161>
35
+ #
36
+ # @return [DateTime] DateTime object representing the same moment as receiver
37
+ #
38
+ # @api public
39
+ remove_method :to_datetime if instance_methods(false).any? { |m| m.to_sym == :to_datetime }
40
+ def to_datetime
41
+ DateTime.new(year, month, day, hour, min, sec, Rational(gmt_offset, 24 * 3600))
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ require "stringio"
2
+
3
+ # To use as a parameter to Merb::Template.inline_template
4
+ class VirtualFile < StringIO
5
+ attr_accessor :path
6
+ def initialize(string, path)
7
+ super(string)
8
+ @path = path
9
+ end
10
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ describe Array do
4
+ before :all do
5
+ @array = [ [ :a, [ 1 ] ], [ :b, [ 2 ] ], [ :c, [ 3 ] ] ].freeze
6
+ end
7
+
8
+ it { @array.should respond_to(:to_hash) }
9
+
10
+ describe '#to_hash' do
11
+ before :all do
12
+ @return = @array.to_hash
13
+ end
14
+
15
+ it 'should return a Hash' do
16
+ @return.should be_kind_of(Hash)
17
+ end
18
+
19
+ it 'should return expected value' do
20
+ @return.should == { :a => [ 1 ], :b => [ 2 ], :c => [ 3 ] }
21
+ end
22
+ end
23
+
24
+ it { @array.should respond_to(:to_mash) }
25
+
26
+ describe '#to_mash' do
27
+ before :all do
28
+ @return = @array.to_mash
29
+ end
30
+
31
+ it 'should return a Mash' do
32
+ @return.should be_kind_of(Mash)
33
+ end
34
+
35
+ it 'should return expected value' do
36
+ @return.should == { 'a' => [ 1 ], 'b' => [ 2 ], 'c' => [ 3 ] }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,85 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ describe Object do
4
+ it 'should provide blank?' do
5
+ Object.new.should respond_to(:blank?)
6
+ end
7
+
8
+ it 'should be blank if it is nil' do
9
+ object = Object.new
10
+ class << object
11
+ def nil?; true end
12
+ end
13
+ object.should be_blank
14
+ end
15
+
16
+ it 'should be blank if it is empty' do
17
+ {}.should be_blank
18
+ [].should be_blank
19
+ end
20
+
21
+ it 'should not be blank if not nil or empty' do
22
+ Object.new.should_not be_blank
23
+ [nil].should_not be_blank
24
+ { nil => 0 }.should_not be_blank
25
+ end
26
+ end
27
+
28
+ describe Numeric do
29
+ it 'should provide blank?' do
30
+ 1.should respond_to(:blank?)
31
+ end
32
+
33
+ it 'should never be blank' do
34
+ 1.should_not be_blank
35
+ end
36
+ end
37
+
38
+ describe NilClass do
39
+ it 'should provide blank?' do
40
+ nil.should respond_to(:blank?)
41
+ end
42
+
43
+ it 'should always be blank' do
44
+ nil.should be_blank
45
+ end
46
+ end
47
+
48
+ describe TrueClass do
49
+ it 'should provide blank?' do
50
+ true.should respond_to(:blank?)
51
+ end
52
+
53
+ it 'should never be blank' do
54
+ true.should_not be_blank
55
+ end
56
+ end
57
+
58
+ describe FalseClass do
59
+ it 'should provide blank?' do
60
+ false.should respond_to(:blank?)
61
+ end
62
+
63
+ it 'should always be blank' do
64
+ false.should be_blank
65
+ end
66
+ end
67
+
68
+ describe String do
69
+ it 'should provide blank?' do
70
+ 'string'.should respond_to(:blank?)
71
+ end
72
+
73
+ it 'should be blank if empty' do
74
+ ''.should be_blank
75
+ end
76
+
77
+ it 'should be blank if it only contains whitespace' do
78
+ ' '.should be_blank
79
+ " \r \n \t ".should be_blank
80
+ end
81
+
82
+ it 'should not be blank if it contains non-whitespace' do
83
+ ' a '.should_not be_blank
84
+ end
85
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ describe Extlib::ByteArray do
4
+ it 'should be a String' do
5
+ Extlib::ByteArray.new.should be_kind_of(String)
6
+ end
7
+ end
@@ -0,0 +1,157 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ class Grandparent
4
+ end
5
+
6
+ class Parent < Grandparent
7
+ end
8
+
9
+ class Child < Parent
10
+ end
11
+
12
+ class Parent
13
+ end
14
+
15
+ class Grandparent
16
+ class_inheritable_accessor :last_name, :_attribute
17
+
18
+ self._attribute = 1900
19
+ end
20
+
21
+ class ClassWithInheritableSymbolAccessor
22
+ class_inheritable_accessor :symbol
23
+ self.symbol = :foo
24
+ end
25
+
26
+ class ClassInheritingSymbolAccessor < ClassWithInheritableSymbolAccessor
27
+ end
28
+
29
+ describe Class, "#inheritable_accessor" do
30
+
31
+ after :each do
32
+ Grandparent.send(:remove_instance_variable, "@last_name") rescue nil
33
+ Parent.send(:remove_instance_variable, "@last_name") rescue nil
34
+ Child.send(:remove_instance_variable, "@last_name") rescue nil
35
+ end
36
+
37
+ it 'inherits from parent unless overriden' do
38
+ Parent._attribute.should == 1900
39
+ Child._attribute.should == 1900
40
+ end
41
+
42
+ it 'inherits from grandparent unless overriden' do
43
+ Child._attribute.should == 1900
44
+ end
45
+
46
+ it "inherits even if the accessor is made after the inheritance" do
47
+ Grandparent.last_name = "Merb"
48
+ Parent.last_name.should == "Merb"
49
+ Child.last_name.should == "Merb"
50
+ end
51
+
52
+ it "supports ||= to change a child" do
53
+ Parent.last_name ||= "Merb"
54
+ Grandparent.last_name.should == nil
55
+ Parent.last_name.should == "Merb"
56
+ Child.last_name.should == "Merb"
57
+ end
58
+
59
+ it "supports << to change a child when the parent is an Array" do
60
+ Grandparent.last_name = ["Merb"]
61
+ Parent.last_name << "Core"
62
+ Grandparent.last_name.should == ["Merb"]
63
+ Parent.last_name.should == ["Merb", "Core"]
64
+ end
65
+
66
+ it "supports ! methods on an Array" do
67
+ Grandparent.last_name = %w(Merb Core)
68
+ Parent.last_name.reverse!
69
+ Grandparent.last_name.should == %w(Merb Core)
70
+ Parent.last_name.should == %w(Core Merb)
71
+ end
72
+
73
+ it "support modifying a parent Hash" do
74
+ Grandparent.last_name = {"Merb" => "name"}
75
+ Parent.last_name["Core"] = "name"
76
+ Parent.last_name.should == {"Merb" => "name", "Core" => "name"}
77
+ Grandparent.last_name.should == {"Merb" => "name"}
78
+ end
79
+
80
+ it "supports hard-merging a parent Hash" do
81
+ Grandparent.last_name = {"Merb" => "name"}
82
+ Parent.last_name.merge!("Core" => "name")
83
+ Parent.last_name.should == {"Merb" => "name", "Core" => "name"}
84
+ Grandparent.last_name.should == {"Merb" => "name"}
85
+ end
86
+
87
+ it "supports changes to the parent even if the child has already been read" do
88
+ Child.last_name
89
+ Grandparent.last_name = "Merb"
90
+ Child.last_name.should == "Merb"
91
+ end
92
+
93
+ it "handles nil being set midstream" do
94
+ Child.last_name
95
+ Parent.last_name = nil
96
+ Grandparent.last_name = "Merb"
97
+ Child.last_name.should == nil
98
+ end
99
+
100
+ it "handles false being used in Parent" do
101
+ Child.last_name
102
+ Parent.last_name = false
103
+ Grandparent.last_name = "Merb"
104
+ Child.last_name.should == false
105
+ end
106
+
107
+ it "handles the grandparent changing the value (as long as the child isn't read first)" do
108
+ Grandparent.last_name = "Merb"
109
+ Grandparent.last_name = "Core"
110
+ Child.last_name.should == "Core"
111
+ end
112
+
113
+ end
114
+
115
+ describe Class, "#inheritable_accessor (of type Symbol)" do
116
+
117
+ it "should not raise" do
118
+ lambda { ClassInheritingSymbolAccessor.symbol }.should_not raise_error(TypeError)
119
+ end
120
+
121
+ end
122
+
123
+ #
124
+ # The bug that prompted this estoric spec was found in
125
+ # the wild when using dm-is-versioned with c_i_w.
126
+ #
127
+
128
+ module Plugin
129
+ def self.included(base)
130
+ base.class_eval do
131
+ class_inheritable_writer :plugin_options
132
+ class_inheritable_reader :plugin_options
133
+ self.plugin_options = :foo
134
+ end
135
+ end
136
+ end
137
+
138
+ class Model
139
+ def self.new
140
+ model = Class.new
141
+ model.send(:include, Plugin)
142
+ model
143
+ end
144
+
145
+ include Plugin
146
+ self.const_set("Version", Model.new)
147
+ end
148
+
149
+ describe Class, "#inheritable_accessor" do
150
+ it "uses object_id for comparison" do
151
+ Model.methods.map { |m| m.to_sym }.should be_include(:plugin_options)
152
+ Model.plugin_options.should == :foo
153
+
154
+ Model::Version.methods.map { |m| m.to_sym }.should be_include(:plugin_options)
155
+ Model::Version.plugin_options.should == :foo
156
+ end
157
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+ require 'json'
3
+
4
+ describe DateTime, "#to_time" do
5
+ before do
6
+ @expected = Time.now.to_s
7
+ @datetime = DateTime.parse(@expected)
8
+ end
9
+
10
+ it "should return a copy of time" do
11
+ time = @datetime.to_time
12
+ time.class.should == Time
13
+ time.to_s.should == @expected
14
+ end
15
+ end
16
+
17
+ describe Time, "#to_datetime" do
18
+ it "should return a copy of its self" do
19
+ datetime = DateTime.now
20
+ datetime.to_datetime.should == datetime
21
+ end
22
+ end
@@ -0,0 +1,537 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+ require "date"
3
+ require 'bigdecimal'
4
+
5
+ describe Hash, "environmentize_keys!" do
6
+ it "should transform keys to uppercase text" do
7
+ { :test_1 => 'test', 'test_2' => 'test', 1 => 'test' }.environmentize_keys!.should ==
8
+ { 'TEST_1' => 'test', 'TEST_2' => 'test', '1' => 'test' }
9
+ end
10
+
11
+ it "should only transform one level of keys" do
12
+ { :test_1 => { :test2 => 'test'} }.environmentize_keys!.should ==
13
+ { 'TEST_1' => { :test2 => 'test'} }
14
+ end
15
+ end
16
+
17
+
18
+ describe Hash, "only" do
19
+ before do
20
+ @hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE', 4 => nil }
21
+ end
22
+
23
+ it "should return a hash with only the given key(s)" do
24
+ @hash.only(:not_in_there).should == {}
25
+ @hash.only(4).should == {4 => nil}
26
+ @hash.only(:one).should == { :one => 'ONE' }
27
+ @hash.only(:one, 3).should == { :one => 'ONE', 3 => 'THREE' }
28
+ end
29
+ end
30
+
31
+
32
+ describe Hash, "except" do
33
+ before do
34
+ @hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE' }
35
+ end
36
+
37
+ it "should return a hash without only the given key(s)" do
38
+ @hash.except(:one).should == { 'two' => 'TWO', 3 => 'THREE' }
39
+ @hash.except(:one, 3).should == { 'two' => 'TWO' }
40
+ end
41
+ end
42
+
43
+
44
+ describe Hash, "to_xml_attributes" do
45
+ before do
46
+ @hash = { :one => "ONE", "two" => "TWO" }
47
+ end
48
+
49
+ it "should turn the hash into xml attributes" do
50
+ attrs = @hash.to_xml_attributes
51
+ attrs.should match(/one="ONE"/m)
52
+ attrs.should match(/two="TWO"/m)
53
+ end
54
+
55
+ it 'should preserve _ in hash keys' do
56
+ attrs = {
57
+ :some_long_attribute => "with short value",
58
+ :crash => :burn,
59
+ :merb => "uses extlib"
60
+ }.to_xml_attributes
61
+
62
+ attrs.should =~ /some_long_attribute="with short value"/
63
+ attrs.should =~ /merb="uses extlib"/
64
+ attrs.should =~ /crash="burn"/
65
+ end
66
+ end
67
+
68
+
69
+ describe Hash, "from_xml" do
70
+ it "should transform a simple tag with content" do
71
+ xml = "<tag>This is the contents</tag>"
72
+ Hash.from_xml(xml).should == { 'tag' => 'This is the contents' }
73
+ end
74
+
75
+ it "should work with cdata tags" do
76
+ xml = <<-END
77
+ <tag>
78
+ <![CDATA[
79
+ text inside cdata
80
+ ]]>
81
+ </tag>
82
+ END
83
+ Hash.from_xml(xml)["tag"].strip.should == "text inside cdata"
84
+ end
85
+
86
+ it "should transform a simple tag with attributes" do
87
+ xml = "<tag attr1='1' attr2='2'></tag>"
88
+ hash = { 'tag' => { 'attr1' => '1', 'attr2' => '2' } }
89
+ Hash.from_xml(xml).should == hash
90
+ end
91
+
92
+ it "should transform repeating siblings into an array" do
93
+ xml =<<-XML
94
+ <opt>
95
+ <user login="grep" fullname="Gary R Epstein" />
96
+ <user login="stty" fullname="Simon T Tyson" />
97
+ </opt>
98
+ XML
99
+
100
+ Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Array)
101
+
102
+ hash = {
103
+ 'opt' => {
104
+ 'user' => [{
105
+ 'login' => 'grep',
106
+ 'fullname' => 'Gary R Epstein'
107
+ },{
108
+ 'login' => 'stty',
109
+ 'fullname' => 'Simon T Tyson'
110
+ }]
111
+ }
112
+ }
113
+
114
+ Hash.from_xml(xml).should == hash
115
+ end
116
+
117
+ it "should not transform non-repeating siblings into an array" do
118
+ xml =<<-XML
119
+ <opt>
120
+ <user login="grep" fullname="Gary R Epstein" />
121
+ </opt>
122
+ XML
123
+
124
+ Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Hash)
125
+
126
+ hash = {
127
+ 'opt' => {
128
+ 'user' => {
129
+ 'login' => 'grep',
130
+ 'fullname' => 'Gary R Epstein'
131
+ }
132
+ }
133
+ }
134
+
135
+ Hash.from_xml(xml).should == hash
136
+ end
137
+
138
+ it "should typecast an integer" do
139
+ xml = "<tag type='integer'>10</tag>"
140
+ Hash.from_xml(xml)['tag'].should == 10
141
+ end
142
+
143
+ it "should typecast a true boolean" do
144
+ xml = "<tag type='boolean'>true</tag>"
145
+ Hash.from_xml(xml)['tag'].should be_true
146
+ end
147
+
148
+ it "should typecast a false boolean" do
149
+ ["false"].each do |w|
150
+ Hash.from_xml("<tag type='boolean'>#{w}</tag>")['tag'].should be_false
151
+ end
152
+ end
153
+
154
+ it "should typecast a datetime" do
155
+ xml = "<tag type='datetime'>2007-12-31 10:32</tag>"
156
+ Hash.from_xml(xml)['tag'].should == Time.parse( '2007-12-31 10:32' ).utc
157
+ end
158
+
159
+ it "should typecast a date" do
160
+ xml = "<tag type='date'>2007-12-31</tag>"
161
+ Hash.from_xml(xml)['tag'].should == Date.parse('2007-12-31')
162
+ end
163
+
164
+ it "should unescape html entities" do
165
+ values = {
166
+ "<" => "&lt;",
167
+ ">" => "&gt;",
168
+ '"' => "&quot;",
169
+ "'" => "&apos;",
170
+ "&" => "&amp;"
171
+ }
172
+ values.each do |k,v|
173
+ xml = "<tag>Some content #{v}</tag>"
174
+ Hash.from_xml(xml)['tag'].should match(Regexp.new(k))
175
+ end
176
+ end
177
+
178
+ it "should undasherize keys as tags" do
179
+ xml = "<tag-1>Stuff</tag-1>"
180
+ Hash.from_xml(xml).should have_key('tag_1')
181
+ end
182
+
183
+ it "should undasherize keys as attributes" do
184
+ xml = "<tag1 attr-1='1'></tag1>"
185
+ Hash.from_xml(xml)['tag1'].should have_key('attr_1')
186
+ end
187
+
188
+ it "should undasherize keys as tags and attributes" do
189
+ xml = "<tag-1 attr-1='1'></tag-1>"
190
+ Hash.from_xml(xml).should have_key('tag_1' )
191
+ Hash.from_xml(xml)['tag_1'].should have_key('attr_1')
192
+ end
193
+
194
+ it "should render nested content correctly" do
195
+ xml = "<root><tag1>Tag1 Content <em><strong>This is strong</strong></em></tag1></root>"
196
+ Hash.from_xml(xml)['root']['tag1'].should == "Tag1 Content <em><strong>This is strong</strong></em>"
197
+ end
198
+
199
+ it "should render nested content with split text nodes correctly" do
200
+ xml = "<root>Tag1 Content<em>Stuff</em> Hi There</root>"
201
+ Hash.from_xml(xml)['root'].should == "Tag1 Content<em>Stuff</em> Hi There"
202
+ end
203
+
204
+ it "should ignore attributes when a child is a text node" do
205
+ xml = "<root attr1='1'>Stuff</root>"
206
+ Hash.from_xml(xml).should == { "root" => "Stuff" }
207
+ end
208
+
209
+ it "should ignore attributes when any child is a text node" do
210
+ xml = "<root attr1='1'>Stuff <em>in italics</em></root>"
211
+ Hash.from_xml(xml).should == { "root" => "Stuff <em>in italics</em>" }
212
+ end
213
+
214
+ it "should correctly transform multiple children" do
215
+ xml = <<-XML
216
+ <user gender='m'>
217
+ <age type='integer'>35</age>
218
+ <name>Home Simpson</name>
219
+ <dob type='date'>1988-01-01</dob>
220
+ <joined-at type='datetime'>2000-04-28 23:01</joined-at>
221
+ <is-cool type='boolean'>true</is-cool>
222
+ </user>
223
+ XML
224
+
225
+ hash = {
226
+ "user" => {
227
+ "gender" => "m",
228
+ "age" => 35,
229
+ "name" => "Home Simpson",
230
+ "dob" => Date.parse('1988-01-01'),
231
+ "joined_at" => Time.parse("2000-04-28 23:01"),
232
+ "is_cool" => true
233
+ }
234
+ }
235
+
236
+ Hash.from_xml(xml).should == hash
237
+ end
238
+
239
+ it "should properly handle nil values (ActiveSupport Compatible)" do
240
+ topic_xml = <<-EOT
241
+ <topic>
242
+ <title></title>
243
+ <id type="integer"></id>
244
+ <approved type="boolean"></approved>
245
+ <written-on type="date"></written-on>
246
+ <viewed-at type="datetime"></viewed-at>
247
+ <content type="yaml"></content>
248
+ <parent-id></parent-id>
249
+ </topic>
250
+ EOT
251
+
252
+ expected_topic_hash = {
253
+ 'title' => nil,
254
+ 'id' => nil,
255
+ 'approved' => nil,
256
+ 'written_on' => nil,
257
+ 'viewed_at' => nil,
258
+ 'content' => nil,
259
+ 'parent_id' => nil
260
+ }
261
+ Hash.from_xml(topic_xml)["topic"].should == expected_topic_hash
262
+ end
263
+
264
+ it "should handle a single record from xml (ActiveSupport Compatible)" do
265
+ topic_xml = <<-EOT
266
+ <topic>
267
+ <title>The First Topic</title>
268
+ <author-name>David</author-name>
269
+ <id type="integer">1</id>
270
+ <approved type="boolean"> true </approved>
271
+ <replies-count type="integer">0</replies-count>
272
+ <replies-close-in type="integer">2592000000</replies-close-in>
273
+ <written-on type="date">2003-07-16</written-on>
274
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
275
+ <content type="yaml">--- \n1: should be an integer\n:message: Have a nice day\narray: \n- should-have-dashes: true\n should_have_underscores: true\n</content>
276
+ <author-email-address>david@loudthinking.com</author-email-address>
277
+ <parent-id></parent-id>
278
+ <ad-revenue type="decimal">1.5</ad-revenue>
279
+ <optimum-viewing-angle type="float">135</optimum-viewing-angle>
280
+ <resident type="symbol">yes</resident>
281
+ </topic>
282
+ EOT
283
+
284
+ expected_topic_hash = {
285
+ 'title' => "The First Topic",
286
+ 'author_name' => "David",
287
+ 'id' => 1,
288
+ 'approved' => true,
289
+ 'replies_count' => 0,
290
+ 'replies_close_in' => 2592000000,
291
+ 'written_on' => Date.new(2003, 7, 16),
292
+ 'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
293
+ # Changed this line where the key is :message. The yaml specifies this as a symbol, and who am I to change what you specify
294
+ # The line in ActiveSupport is
295
+ # 'content' => { 'message' => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
296
+ 'content' => { :message => "Have a nice day", 1 => "should be an integer", "array" => [{ "should-have-dashes" => true, "should_have_underscores" => true }] },
297
+ 'author_email_address' => "david@loudthinking.com",
298
+ 'parent_id' => nil,
299
+ 'ad_revenue' => BigDecimal("1.50"),
300
+ 'optimum_viewing_angle' => 135.0,
301
+ 'resident' => :yes
302
+ }
303
+
304
+ Hash.from_xml(topic_xml)["topic"].each do |k,v|
305
+ v.should == expected_topic_hash[k]
306
+ end
307
+ end
308
+
309
+ it "should handle multiple records (ActiveSupport Compatible)" do
310
+ topics_xml = <<-EOT
311
+ <topics type="array">
312
+ <topic>
313
+ <title>The First Topic</title>
314
+ <author-name>David</author-name>
315
+ <id type="integer">1</id>
316
+ <approved type="boolean">false</approved>
317
+ <replies-count type="integer">0</replies-count>
318
+ <replies-close-in type="integer">2592000000</replies-close-in>
319
+ <written-on type="date">2003-07-16</written-on>
320
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
321
+ <content>Have a nice day</content>
322
+ <author-email-address>david@loudthinking.com</author-email-address>
323
+ <parent-id nil="true"></parent-id>
324
+ </topic>
325
+ <topic>
326
+ <title>The Second Topic</title>
327
+ <author-name>Jason</author-name>
328
+ <id type="integer">1</id>
329
+ <approved type="boolean">false</approved>
330
+ <replies-count type="integer">0</replies-count>
331
+ <replies-close-in type="integer">2592000000</replies-close-in>
332
+ <written-on type="date">2003-07-16</written-on>
333
+ <viewed-at type="datetime">2003-07-16T09:28:00+0000</viewed-at>
334
+ <content>Have a nice day</content>
335
+ <author-email-address>david@loudthinking.com</author-email-address>
336
+ <parent-id></parent-id>
337
+ </topic>
338
+ </topics>
339
+ EOT
340
+
341
+ expected_topic_hash = {
342
+ 'title' => "The First Topic",
343
+ 'author_name' => "David",
344
+ 'id' => 1,
345
+ 'approved' => false,
346
+ 'replies_count' => 0,
347
+ 'replies_close_in' => 2592000000,
348
+ 'written_on' => Date.new(2003, 7, 16),
349
+ 'viewed_at' => Time.utc(2003, 7, 16, 9, 28),
350
+ 'content' => "Have a nice day",
351
+ 'author_email_address' => "david@loudthinking.com",
352
+ 'parent_id' => nil
353
+ }
354
+ # puts Hash.from_xml(topics_xml)['topics'].first.inspect
355
+ Hash.from_xml(topics_xml)["topics"].first.each do |k,v|
356
+ v.should == expected_topic_hash[k]
357
+ end
358
+ end
359
+
360
+ it "should handle a single record from_xml with attributes other than type (ActiveSupport Compatible)" do
361
+ topic_xml = <<-EOT
362
+ <rsp stat="ok">
363
+ <photos page="1" pages="1" perpage="100" total="16">
364
+ <photo id="175756086" owner="55569174@N00" secret="0279bf37a1" server="76" title="Colored Pencil PhotoBooth Fun" ispublic="1" isfriend="0" isfamily="0"/>
365
+ </photos>
366
+ </rsp>
367
+ EOT
368
+
369
+ expected_topic_hash = {
370
+ 'id' => "175756086",
371
+ 'owner' => "55569174@N00",
372
+ 'secret' => "0279bf37a1",
373
+ 'server' => "76",
374
+ 'title' => "Colored Pencil PhotoBooth Fun",
375
+ 'ispublic' => "1",
376
+ 'isfriend' => "0",
377
+ 'isfamily' => "0",
378
+ }
379
+ Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"].each do |k,v|
380
+ v.should == expected_topic_hash[k]
381
+ end
382
+ end
383
+
384
+ it "should handle an emtpy array (ActiveSupport Compatible)" do
385
+ blog_xml = <<-XML
386
+ <blog>
387
+ <posts type="array"></posts>
388
+ </blog>
389
+ XML
390
+ expected_blog_hash = {"blog" => {"posts" => []}}
391
+ Hash.from_xml(blog_xml).should == expected_blog_hash
392
+ end
393
+
394
+ it "should handle empty array with whitespace from xml (ActiveSupport Compatible)" do
395
+ blog_xml = <<-XML
396
+ <blog>
397
+ <posts type="array">
398
+ </posts>
399
+ </blog>
400
+ XML
401
+ expected_blog_hash = {"blog" => {"posts" => []}}
402
+ Hash.from_xml(blog_xml).should == expected_blog_hash
403
+ end
404
+
405
+ it "should handle array with one entry from_xml (ActiveSupport Compatible)" do
406
+ blog_xml = <<-XML
407
+ <blog>
408
+ <posts type="array">
409
+ <post>a post</post>
410
+ </posts>
411
+ </blog>
412
+ XML
413
+ expected_blog_hash = {"blog" => {"posts" => ["a post"]}}
414
+ Hash.from_xml(blog_xml).should == expected_blog_hash
415
+ end
416
+
417
+ it "should handle array with multiple entries from xml (ActiveSupport Compatible)" do
418
+ blog_xml = <<-XML
419
+ <blog>
420
+ <posts type="array">
421
+ <post>a post</post>
422
+ <post>another post</post>
423
+ </posts>
424
+ </blog>
425
+ XML
426
+ expected_blog_hash = {"blog" => {"posts" => ["a post", "another post"]}}
427
+ Hash.from_xml(blog_xml).should == expected_blog_hash
428
+ end
429
+
430
+ it "should handle file types (ActiveSupport Compatible)" do
431
+ blog_xml = <<-XML
432
+ <blog>
433
+ <logo type="file" name="logo.png" content_type="image/png">
434
+ </logo>
435
+ </blog>
436
+ XML
437
+ hash = Hash.from_xml(blog_xml)
438
+ hash.should have_key('blog')
439
+ hash['blog'].should have_key('logo')
440
+
441
+ file = hash['blog']['logo']
442
+ file.original_filename.should == 'logo.png'
443
+ file.content_type.should == 'image/png'
444
+ end
445
+
446
+ it "should handle file from xml with defaults (ActiveSupport Compatible)" do
447
+ blog_xml = <<-XML
448
+ <blog>
449
+ <logo type="file">
450
+ </logo>
451
+ </blog>
452
+ XML
453
+ file = Hash.from_xml(blog_xml)['blog']['logo']
454
+ file.original_filename.should == 'untitled'
455
+ file.content_type.should == 'application/octet-stream'
456
+ end
457
+
458
+ it "should handle xsd like types from xml (ActiveSupport Compatible)" do
459
+ bacon_xml = <<-EOT
460
+ <bacon>
461
+ <weight type="double">0.5</weight>
462
+ <price type="decimal">12.50</price>
463
+ <chunky type="boolean"> 1 </chunky>
464
+ <expires-at type="dateTime">2007-12-25T12:34:56+0000</expires-at>
465
+ <notes type="string"></notes>
466
+ <illustration type="base64Binary">YmFiZS5wbmc=</illustration>
467
+ </bacon>
468
+ EOT
469
+
470
+ expected_bacon_hash = {
471
+ 'weight' => 0.5,
472
+ 'chunky' => true,
473
+ 'price' => BigDecimal("12.50"),
474
+ 'expires_at' => Time.utc(2007,12,25,12,34,56),
475
+ 'notes' => "",
476
+ 'illustration' => "babe.png"
477
+ }
478
+
479
+ Hash.from_xml(bacon_xml)["bacon"].should == expected_bacon_hash
480
+ end
481
+
482
+ it "should let type trickle through when unknown (ActiveSupport Compatible)" do
483
+ product_xml = <<-EOT
484
+ <product>
485
+ <weight type="double">0.5</weight>
486
+ <image type="ProductImage"><filename>image.gif</filename></image>
487
+
488
+ </product>
489
+ EOT
490
+
491
+ expected_product_hash = {
492
+ 'weight' => 0.5,
493
+ 'image' => {'type' => 'ProductImage', 'filename' => 'image.gif' },
494
+ }
495
+
496
+ Hash.from_xml(product_xml)["product"].should == expected_product_hash
497
+ end
498
+
499
+ it "should handle unescaping from xml (ActiveResource Compatible)" do
500
+ xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
501
+ expected_hash = {
502
+ 'bare_string' => 'First & Last Name',
503
+ 'pre_escaped_string' => 'First &amp; Last Name'
504
+ }
505
+
506
+ Hash.from_xml(xml_string)['person'].should == expected_hash
507
+ end
508
+ end
509
+
510
+ describe Hash, 'to_params' do
511
+ {
512
+ { "foo" => "bar", "baz" => "bat" } => "foo=bar&baz=bat",
513
+ { "foo" => [ "bar", "baz" ] } => "foo[]=bar&foo[]=baz",
514
+ { "foo" => [ {"bar" => "1"}, {"bar" => 2} ] } => "foo[][bar]=1&foo[][bar]=2",
515
+ { "foo" => { "bar" => [ {"baz" => 1}, {"baz" => "2"} ] } } => "foo[bar][][baz]=1&foo[bar][][baz]=2",
516
+ { "foo" => {"1" => "bar", "2" => "baz"} } => "foo[1]=bar&foo[2]=baz"
517
+ }.each do |hash, params|
518
+ it "should covert hash: #{hash.inspect} to params: #{params.inspect}" do
519
+ hash.to_params.split('&').sort.should == params.split('&').sort
520
+ end
521
+ end
522
+
523
+ it 'should not leave a trailing &' do
524
+ { :name => 'Bob', :address => { :street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222'] } }.to_params.should_not match(/&$/)
525
+ end
526
+ end
527
+
528
+ describe Hash, 'to_mash' do
529
+ before :each do
530
+ @hash = Hash.new(10)
531
+ end
532
+
533
+ it "copies default Hash value to Mash" do
534
+ @mash = @hash.to_mash
535
+ @mash[:merb].should == 10
536
+ end
537
+ end