merb 0.4.2 → 0.5.0

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 (108) hide show
  1. data/README +21 -14
  2. data/Rakefile +157 -108
  3. data/SVN_REVISION +1 -0
  4. data/app_generators/merb/templates/Rakefile +20 -4
  5. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +1 -1
  6. data/app_generators/merb/templates/config/boot.rb +1 -1
  7. data/app_generators/merb/templates/config/dependencies.rb +3 -3
  8. data/app_generators/merb/templates/config/merb.yml +5 -0
  9. data/app_generators/merb/templates/config/merb_init.rb +3 -3
  10. data/app_generators/merb/templates/script/destroy +3 -0
  11. data/app_generators/merb/templates/script/generate +1 -1
  12. data/app_generators/merb/templates/spec/spec_helper.rb +2 -2
  13. data/app_generators/merb/templates/test/test_helper.rb +1 -1
  14. data/app_generators/merb_plugin/merb_plugin_generator.rb +4 -0
  15. data/bin/merb +1 -3
  16. data/lib/merb.rb +144 -76
  17. data/lib/merb/abstract_controller.rb +6 -5
  18. data/lib/merb/assets.rb +119 -0
  19. data/lib/merb/boot_loader.rb +217 -0
  20. data/lib/merb/caching.rb +1 -1
  21. data/lib/merb/caching/action_cache.rb +1 -1
  22. data/lib/merb/caching/fragment_cache.rb +1 -1
  23. data/lib/merb/caching/store/file_cache.rb +1 -1
  24. data/lib/merb/config.rb +290 -0
  25. data/lib/merb/controller.rb +5 -5
  26. data/lib/merb/core_ext/get_args.rb +1 -0
  27. data/lib/merb/core_ext/hash.rb +182 -169
  28. data/lib/merb/core_ext/kernel.rb +57 -26
  29. data/lib/merb/dispatcher.rb +6 -6
  30. data/lib/merb/drb_server.rb +1 -1
  31. data/lib/merb/generators/merb_generator_helpers.rb +7 -6
  32. data/lib/merb/logger.rb +1 -1
  33. data/lib/merb/mail_controller.rb +3 -4
  34. data/lib/merb/mailer.rb +2 -2
  35. data/lib/merb/mixins/basic_authentication.rb +2 -2
  36. data/lib/merb/mixins/controller.rb +1 -1
  37. data/lib/merb/mixins/general_controller.rb +13 -20
  38. data/lib/merb/mixins/inline_partial.rb +32 -0
  39. data/lib/merb/mixins/render.rb +3 -3
  40. data/lib/merb/mixins/responder.rb +1 -1
  41. data/lib/merb/mixins/view_context.rb +159 -33
  42. data/lib/merb/mongrel_handler.rb +9 -9
  43. data/lib/merb/plugins.rb +1 -1
  44. data/lib/merb/request.rb +25 -1
  45. data/lib/merb/router.rb +264 -226
  46. data/lib/merb/server.rb +66 -560
  47. data/lib/merb/session/cookie_store.rb +14 -13
  48. data/lib/merb/session/mem_cache_session.rb +20 -10
  49. data/lib/merb/session/memory_session.rb +21 -11
  50. data/lib/merb/template.rb +2 -2
  51. data/lib/merb/template/erubis.rb +3 -33
  52. data/lib/merb/template/haml.rb +8 -3
  53. data/lib/merb/test/fake_request.rb +8 -3
  54. data/lib/merb/test/helper.rb +66 -22
  55. data/lib/merb/test/rspec.rb +9 -155
  56. data/lib/merb/test/rspec_matchers/controller_matchers.rb +117 -0
  57. data/lib/merb/test/rspec_matchers/markup_matchers.rb +98 -0
  58. data/lib/merb/upload_handler.rb +2 -1
  59. data/lib/merb/version.rb +38 -3
  60. data/lib/merb/view_context.rb +1 -2
  61. data/lib/tasks/merb.rake +11 -11
  62. data/merb_generators/part_controller/USAGE +5 -0
  63. data/merb_generators/part_controller/part_controller_generator.rb +27 -0
  64. data/merb_generators/part_controller/templates/controller.rb +8 -0
  65. data/merb_generators/part_controller/templates/helper.rb +5 -0
  66. data/merb_generators/part_controller/templates/index.html.erb +3 -0
  67. data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +1 -1
  68. data/script/destroy +14 -0
  69. data/script/generate +14 -0
  70. data/spec/fixtures/controllers/dispatch_spec_controllers.rb +9 -1
  71. data/spec/fixtures/controllers/render_spec_controllers.rb +5 -5
  72. data/spec/fixtures/models/router_spec_models.rb +10 -0
  73. data/spec/merb/abstract_controller_spec.rb +2 -2
  74. data/spec/merb/assets_spec.rb +207 -0
  75. data/spec/merb/caching_spec.rb +2 -2
  76. data/spec/merb/controller_spec.rb +7 -2
  77. data/spec/merb/cookie_store_spec.rb +1 -1
  78. data/spec/merb/core_ext/class_spec.rb +97 -0
  79. data/spec/merb/core_ext/enumerable_spec.rb +27 -0
  80. data/spec/merb/core_ext/hash_spec.rb +251 -0
  81. data/spec/merb/core_ext/inflector_spec.rb +34 -0
  82. data/spec/merb/core_ext/kernel_spec.rb +25 -0
  83. data/spec/merb/core_ext/numeric_spec.rb +26 -0
  84. data/spec/merb/core_ext/object_spec.rb +47 -0
  85. data/spec/merb/core_ext/string_spec.rb +22 -0
  86. data/spec/merb/core_ext/symbol_spec.rb +7 -0
  87. data/spec/merb/dependency_spec.rb +22 -0
  88. data/spec/merb/dispatch_spec.rb +23 -12
  89. data/spec/merb/fake_request_spec.rb +8 -0
  90. data/spec/merb/generator_spec.rb +140 -21
  91. data/spec/merb/handler_spec.rb +5 -5
  92. data/spec/merb/mail_controller_spec.rb +3 -3
  93. data/spec/merb/render_spec.rb +1 -1
  94. data/spec/merb/responder_spec.rb +3 -3
  95. data/spec/merb/router_spec.rb +260 -191
  96. data/spec/merb/server_spec.rb +5 -5
  97. data/spec/merb/upload_handler_spec.rb +7 -0
  98. data/spec/merb/version_spec.rb +33 -0
  99. data/spec/merb/view_context_spec.rb +217 -59
  100. data/spec/spec_generator_helper.rb +15 -0
  101. data/spec/spec_helper.rb +5 -3
  102. data/spec/spec_helpers/url_shared_behaviour.rb +5 -7
  103. metadata +32 -7
  104. data/lib/merb/caching/store/memcache.rb +0 -20
  105. data/lib/merb/mixins/form_control.rb +0 -332
  106. data/lib/patch +0 -69
  107. data/spec/merb/core_ext_spec.rb +0 -464
  108. data/spec/merb/form_control_mixin_spec.rb +0 -431
@@ -38,8 +38,8 @@ class FakeModelWithArguments
38
38
  end
39
39
 
40
40
  def to_xml(*args)
41
- options = args.last.is_a?(Hash) ? args.pop.stringify_keys : {}
42
- options.keys.sort.inject('') do |str, tag|
41
+ options = args.last.is_a?(Hash) ? args.pop : {}
42
+ options.keys.sort_by{|s| s.to_s}.inject('') do |str, tag|
43
43
  str << "<#{tag}>#{options[tag]}</#{tag}>"
44
44
  end
45
45
  end
@@ -169,6 +169,6 @@ class ExtensionTemplateController < Merb::Controller
169
169
 
170
170
  end
171
171
 
172
- Merb::Server.load_action_arguments
173
- Merb::Server.load_controller_template_path_cache
174
- Merb::Server.load_erubis_inline_helpers
172
+ Merb::BootLoader.load_action_arguments
173
+ Merb::BootLoader.load_controller_template_path_cache
174
+ Merb::BootLoader.load_inline_helpers
@@ -18,3 +18,13 @@ class Comment
18
18
  42
19
19
  end
20
20
  end
21
+
22
+
23
+ class Blog
24
+ def id
25
+ 42
26
+ end
27
+ def to_param
28
+ "hello#{id}"
29
+ end
30
+ end
@@ -13,8 +13,8 @@ describe Merb::AbstractController do
13
13
  end
14
14
 
15
15
  after(:all) do
16
- Merb::Server.load_controller_template_path_cache
17
- Merb::Server.load_erubis_inline_helpers
16
+ Merb::BootLoader.load_controller_template_path_cache
17
+ Merb::BootLoader.load_inline_helpers
18
18
  end
19
19
 
20
20
  it "should add a template path" do
@@ -0,0 +1,207 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ require "fileutils"
4
+
5
+ describe Merb::Assets, "in the production environment" do
6
+
7
+ before(:each) do
8
+ @old_env = Merb::Config[:environment]
9
+ Merb::Config[:environment] = :production
10
+ Merb::Config.delete(:bundle_assets)
11
+ end
12
+
13
+ after(:each) do
14
+ Merb::Config[:environment] = @old_env
15
+ end
16
+
17
+ it "should bundle assets" do
18
+ Merb::Assets.bundle?.should == true
19
+ end
20
+
21
+ end
22
+
23
+ describe Merb::Assets, "in the development environment" do
24
+
25
+ before(:each) do
26
+ @old_env = Merb::Config[:environment]
27
+ Merb::Config[:environment] = :development
28
+ Merb::Config.delete(:bundle_assets)
29
+ end
30
+
31
+ after(:each) do
32
+ Merb::Config[:environment] = @old_env
33
+ end
34
+
35
+ it "should not bundle assets" do
36
+ Merb::Assets.bundle?.should == false
37
+ end
38
+
39
+ end
40
+
41
+ describe Merb::Assets, "in the development environment with asset bundling enabled" do
42
+
43
+ before(:each) do
44
+ @old_env = Merb::Config[:environment]
45
+ Merb::Config[:environment] = :development
46
+ Merb::Config[:bundle_assets] = true
47
+ end
48
+
49
+ after(:each) do
50
+ Merb::Config[:environment] = @old_env
51
+ end
52
+
53
+ it "should bundle assets" do
54
+ Merb::Assets.bundle?.should == true
55
+ end
56
+
57
+ end
58
+
59
+ describe Merb::Assets::AssetHelpers, "asset_path" do
60
+
61
+ include Merb::Assets::AssetHelpers
62
+
63
+ it "should turn a symbol shorthand for a javascript asset into a URI path" do
64
+ asset_path(:javascript, :dingo).should == "/javascripts/dingo.js"
65
+ end
66
+
67
+ it "should turn a string shorthand for a javascript asset into a URI path" do
68
+ asset_path(:javascript, "dingo").should == "/javascripts/dingo.js"
69
+ asset_path(:javascript, "dingo.r7337").should == "/javascripts/dingo.r7337.js"
70
+ asset_path(:javascript, "dingo/wakka").should == "/javascripts/dingo/wakka.js"
71
+ end
72
+
73
+ it "should turn the filename of a javascript asset into a URI path" do
74
+ asset_path(:javascript, "dingo.js").should == "/javascripts/dingo.js"
75
+ end
76
+
77
+ it "should turn a symbol shorthand for a stylesheet asset into a URI path" do
78
+ asset_path(:stylesheet, :dingo).should == "/stylesheets/dingo.css"
79
+ end
80
+
81
+ it "should turn a string shorthand for a stylesheet asset into a URI path" do
82
+ asset_path(:stylesheet, "dingo").should == "/stylesheets/dingo.css"
83
+ asset_path(:stylesheet, "dingo.r7337").should == "/stylesheets/dingo.r7337.css"
84
+ asset_path(:stylesheet, "dingo/wakka").should == "/stylesheets/dingo/wakka.css"
85
+ end
86
+
87
+ it "should turn the filename of a stylesheet asset into a URI path" do
88
+ asset_path(:stylesheet, "dingo.css").should == "/stylesheets/dingo.css"
89
+ end
90
+
91
+ it "should turn an asset name into a file path" do
92
+ asset_path(:stylesheet, :dingo, true).should == "public/stylesheets/dingo.css"
93
+ end
94
+
95
+ it "should prepend any path prefix to a URI path" do
96
+ begin
97
+ Merb::Config[:path_prefix] = '/inky'
98
+ asset_path(:stylesheet, "dingo.css").should == "/inky/stylesheets/dingo.css"
99
+ ensure
100
+ Merb::Config.delete(:path_prefix)
101
+ end
102
+ end
103
+
104
+ it "should prepend any path prefix to a local path" do
105
+ begin
106
+ Merb::Config[:path_prefix] = '/inky'
107
+ asset_path(:stylesheet, "dingo.css", true).should == "public/stylesheets/dingo.css"
108
+ ensure
109
+ Merb::Config.delete(:path_prefix)
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "an asset bundler with callbacks", :shared => true do
115
+ it "should collect callbacks for execution after bundling" do
116
+ @bundler_klass.add_callback { |filename| "yay #{filename}" }
117
+ @bundler_klass.callbacks.should have(1).procs
118
+ @bundler_klass.callbacks.first.call("me").should == "yay me"
119
+ @bundler_klass.callbacks.clear
120
+ end
121
+ end
122
+
123
+ describe "an asset bundler", :shared => true do
124
+
125
+ include Merb::Assets::AssetHelpers
126
+
127
+ before(:each) do
128
+ FileUtils.mkdir_p("./public/#{@bundler_klass.asset_type}s")
129
+ create_fixture_files
130
+ end
131
+
132
+ after(:each) do
133
+ FileUtils.rm_rf("./public")
134
+ end
135
+
136
+ def create_fixture_files
137
+ @asset1 = :asset1
138
+ @asset1filename = asset_path(@bundler_klass.asset_type, @asset1, true)
139
+ File.open(@asset1filename, "w") { |f| f << "one" }
140
+
141
+ @asset2 = :asset2
142
+ @asset2filename = asset_path(@bundler_klass.asset_type, @asset2, true)
143
+ File.open(@asset2filename, "w") { |f| f << "two" }
144
+
145
+ @asset3 = :asset3
146
+ @asset3filename = asset_path(@bundler_klass.asset_type, @asset3, true)
147
+ File.open(@asset3filename, "w") { |f| f << "three" }
148
+ end
149
+
150
+ it "should combine files into a single file" do
151
+ bundler = @bundler_klass.new(true, @asset1, @asset2, @asset3)
152
+ bundle_name = bundler.bundle!
153
+ File.read(asset_path(@bundler_klass.asset_type, bundle_name, true)).should == "one\ntwo\nthree\n"
154
+ end
155
+
156
+ it "should not overwrite bundles" do
157
+ File.open(asset_path(@bundler_klass.asset_type, :all, true), "w") { |f| f << "exists" }
158
+ bundler = @bundler_klass.new(true, @asset1, @asset2, @asset3)
159
+ bundle_name = bundler.bundle!
160
+ File.read(asset_path(@bundler_klass.asset_type, bundle_name, true)).should == "exists"
161
+ end
162
+
163
+ it "should execute all callbacks, passing them the bundled file's path" do
164
+ begin
165
+ @bundler_klass.add_callback do |filename|
166
+ File.open(filename, "a") do |f|
167
+ f.puts "yay"
168
+ end
169
+ end
170
+
171
+ bundler = @bundler_klass.new(true, @asset1, @asset2, @asset3)
172
+ bundle_name = bundler.bundle!
173
+ File.read(asset_path(@bundler_klass.asset_type, bundle_name, true)).should == "one\ntwo\nthree\nyay\n"
174
+ ensure
175
+ @bundler_klass.callbacks.clear
176
+ end
177
+ end
178
+
179
+ end
180
+
181
+ describe Merb::Assets::JavascriptAssetBundler do
182
+
183
+ before(:each) do
184
+ @bundler_klass = Merb::Assets::JavascriptAssetBundler
185
+ end
186
+
187
+ it "should handle javascript assets" do
188
+ @bundler_klass.asset_type.should == :javascript
189
+ end
190
+
191
+ it_should_behave_like "an asset bundler"
192
+ it_should_behave_like "an asset bundler with callbacks"
193
+ end
194
+
195
+ describe Merb::Assets::StylesheetAssetBundler do
196
+
197
+ before(:each) do
198
+ @bundler_klass = Merb::Assets::StylesheetAssetBundler
199
+ end
200
+
201
+ it "should handle stylesheet assets" do
202
+ @bundler_klass.asset_type.should == :stylesheet
203
+ end
204
+
205
+ it_should_behave_like "an asset bundler"
206
+ it_should_behave_like "an asset bundler with callbacks"
207
+ end
@@ -83,7 +83,7 @@ end
83
83
 
84
84
  describe "fragment caching in memory" do
85
85
  before do
86
- Merb::Server.config['cache_store'] = 'memory'
86
+ Merb::Config['cache_store'] = 'memory'
87
87
  @c = Merb::Caching::Fragment
88
88
  @c.clear
89
89
  end
@@ -93,7 +93,7 @@ end
93
93
 
94
94
  describe "fragment caching to a file" do
95
95
  before do
96
- Merb::Server.config[:cache_store] = 'file'
96
+ Merb::Config[:cache_store] = 'file'
97
97
  @c = Merb::Caching::Fragment
98
98
  @c.clear
99
99
  end
@@ -13,11 +13,16 @@ describe "Merb::Controller" do
13
13
  c = new_controller
14
14
  c._layout.should == :application
15
15
  end
16
-
16
+
17
17
  it "should have a spec helper to dispatch that skips the router" do
18
18
  Merb::Router.should_not_receive(:match)
19
19
  dispatch_to(Bar, :foo, :id => "1") do |controller|
20
- controller.should_receive(:foo).with("1")
20
+ if defined? ParseTreeArray # We have parameterized actions
21
+ controller.should_receive(:foo).with("1")
22
+ else
23
+ controller.should_receive(:foo) # No parameterized actions
24
+ controller.params[:id].should == "1"
25
+ end
21
26
  end
22
27
  end
23
28
 
@@ -18,7 +18,7 @@ class TestCookieSessionController < Merb::Controller
18
18
 
19
19
  end
20
20
 
21
- Merb::Server.config[:session_secret_key] = 'Secret!'
21
+ Merb::Config[:session_secret_key] = 'Secret!'
22
22
 
23
23
  describe Merb::SessionMixin do
24
24
  it "should set the cookie if the cookie is changed" do
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ # Class cattr_reader
4
+
5
+ class ClassWithCAttrReader
6
+ cattr_reader :bacon
7
+ def initialize; @@bacon = "chunky"; end
8
+ end
9
+
10
+ describe "Core Class with cattr_reader", :shared => true do
11
+
12
+ it "should read value from attribute" do
13
+ @klass.bacon.should == "chunky"
14
+ end
15
+
16
+ it "should not write to attribute" do
17
+ lambda {
18
+ @klass.bacon = "soggy"
19
+ }.should raise_error(NoMethodError)
20
+ end
21
+
22
+ end
23
+
24
+ describe Class, "with cattr_reader" do
25
+
26
+ before do
27
+ @klass = ClassWithCAttrReader.new.class
28
+ end
29
+ it_should_behave_like "Core Class with cattr_reader"
30
+
31
+ end
32
+
33
+ describe Class, "with cattr_reader (instantiated)" do
34
+
35
+ before do
36
+ @klass = ClassWithCAttrReader.new
37
+ end
38
+ it_should_behave_like "Core Class with cattr_reader"
39
+
40
+ end
41
+
42
+ # Class cattr_writer
43
+
44
+ class ClassWithCAttrWriter
45
+ cattr_writer :bacon
46
+ def self.chunky?; @@bacon == "chunky"; end
47
+ def chunky?; self.class.chunky?; end
48
+ end
49
+
50
+ describe "Core Class with cattr_writer", :shared => true do
51
+
52
+ it "should write value to attribute" do
53
+ @klass.should be_chunky
54
+ end
55
+
56
+ it "should not read attribute" do
57
+ lambda {
58
+ @klass.bacon
59
+ }.should raise_error(NoMethodError)
60
+ end
61
+
62
+ end
63
+
64
+ describe Class, "with cattr_writer" do
65
+
66
+ before do
67
+ @klass = ClassWithCAttrWriter.new.class
68
+ @klass.bacon = "chunky"
69
+ end
70
+ it_should_behave_like "Core Class with cattr_writer"
71
+
72
+ end
73
+
74
+ describe Class, "with cattr_writer (instantiated)" do
75
+
76
+ before do
77
+ @klass = ClassWithCAttrWriter.new
78
+ @klass.bacon = "chunky"
79
+ end
80
+ it_should_behave_like "Core Class with cattr_writer"
81
+
82
+ end
83
+
84
+ class ClassWithAttrInitialize
85
+ attr_initialize :dog, :muppet
86
+ attr_accessor :dog, :muppet
87
+ end
88
+
89
+ describe ClassWithAttrInitialize do
90
+
91
+ it "should initialize with values" do
92
+ c = ClassWithAttrInitialize.new("louie", "bert")
93
+ c.dog.should == "louie"
94
+ c.muppet.should == "bert"
95
+ end
96
+
97
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Enumerable do
4
+ before do
5
+ @mascots = ['louie', 'bert', 'ernie']
6
+ end
7
+
8
+ it "should perform injecting" do
9
+ @mascots.injecting({}) { |m,i| m[i] = i.size }.should ==
10
+ { 'louie' => 5, 'bert' => 4, 'ernie' => 5 }
11
+ end
12
+
13
+ it "should find arrays of things inside other arrays" do
14
+ @mascots.include_any?('louie', 'sasquatch').should be_true
15
+ end
16
+
17
+ it "should recognize absence of arrays of things inside other arrays" do
18
+ @mascots.include_any?('chicken', 'sasquatch').should be_false
19
+ end
20
+
21
+ it "should group by" do
22
+ groups = (1..6).group_by{ |i| i % 3 }
23
+ groups[0].should == [3,6]
24
+ groups[1].should == [1,4]
25
+ groups[2].should == [2,5]
26
+ end
27
+ end
@@ -0,0 +1,251 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Hash, "environmentize_keys!" do
4
+ it "should transform keys to uppercase text" do
5
+ { :test_1 => 'test', 'test_2' => 'test', 1 => 'test' }.environmentize_keys!.should ==
6
+ { 'TEST_1' => 'test', 'TEST_2' => 'test', '1' => 'test' }
7
+ end
8
+
9
+ it "should only transform one level of keys" do
10
+ { :test_1 => { :test2 => 'test'} }.environmentize_keys!.should ==
11
+ { 'TEST_1' => { :test2 => 'test'} }
12
+ end
13
+ end
14
+
15
+ describe Hash, "only" do
16
+ before do
17
+ @hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE' }
18
+ end
19
+
20
+ it "should return a hash with only the given key(s)" do
21
+ @hash.only(:one).should == { :one => 'ONE' }
22
+ @hash.only(:one, 3).should == { :one => 'ONE', 3 => 'THREE' }
23
+ end
24
+ end
25
+
26
+ describe Hash, "except" do
27
+ before do
28
+ @hash = { :one => 'ONE', 'two' => 'TWO', 3 => 'THREE' }
29
+ end
30
+
31
+ it "should return a hash without only the given key(s)" do
32
+ @hash.except(:one).should == { 'two' => 'TWO', 3 => 'THREE' }
33
+ @hash.except(:one, 3).should == { 'two' => 'TWO' }
34
+ end
35
+ end
36
+
37
+ describe Hash, "symbolize_keys!" do
38
+ before do
39
+ @hash = { 'one' => 1, 'two' => 2 }
40
+ @hash_with_another_hash = { 'a' => 'A', 'prefs' => { 'private' => true, 'sex' => 'yes please' } }
41
+ @hash_with_non_string_keys = { 'one' => 1, 2 => 'TWO', '' => 'blank' }
42
+ end
43
+
44
+ it "should convert all keys to symbols" do
45
+ @hash.symbolize_keys!
46
+ @hash.should == { :one => 1, :two => 2 }
47
+ end
48
+
49
+ it "should recursively convert all keys to symbols" do
50
+ @hash_with_another_hash.symbolize_keys!
51
+ @hash_with_another_hash.should == { :a => 'A', :prefs => { :private => true, :sex => 'yes please' } }
52
+ end
53
+ end
54
+
55
+ describe Hash, "to_xml_attributes" do
56
+ before do
57
+ @hash = { :one => "ONE", "two" => "TWO" }
58
+ end
59
+
60
+ it "should turn the hash into xml attributes" do
61
+ attrs = @hash.to_xml_attributes
62
+ attrs.should match(/one="ONE"/m)
63
+ attrs.should match(/two="TWO"/m)
64
+ end
65
+ end
66
+
67
+ describe Hash, "from_xml" do
68
+ it "should transform a simple tag with content" do
69
+ xml = "<tag>This is the contents</tag>"
70
+ Hash.from_xml(xml).should == { 'tag' => 'This is the contents' }
71
+ end
72
+
73
+ it "should work with cdata tags" do
74
+ xml = <<-END
75
+ <tag>
76
+ <![CDATA[
77
+ text inside cdata
78
+ ]]>
79
+ </tag>
80
+ END
81
+ Hash.from_xml(xml)["tag"].strip.should == "text inside cdata"
82
+ end
83
+
84
+ it "should transform a simple tag with attributes" do
85
+ xml = "<tag attr1='1' attr2='2'></tag>"
86
+ hash = { 'tag' => { 'attr1' => '1', 'attr2' => '2' } }
87
+ Hash.from_xml(xml).should == hash
88
+ end
89
+
90
+ it "should transform repeating siblings into an array" do
91
+ xml =<<-XML
92
+ <opt>
93
+ <user login="grep" fullname="Gary R Epstein" />
94
+ <user login="stty" fullname="Simon T Tyson" />
95
+ </opt>
96
+ XML
97
+
98
+ Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Array)
99
+
100
+ hash = {
101
+ 'opt' => {
102
+ 'user' => [{
103
+ 'login' => 'grep',
104
+ 'fullname' => 'Gary R Epstein'
105
+ },{
106
+ 'login' => 'stty',
107
+ 'fullname' => 'Simon T Tyson'
108
+ }]
109
+ }
110
+ }
111
+
112
+ Hash.from_xml(xml).should == hash
113
+ end
114
+
115
+ it "should not transform non-repeating siblings into an array" do
116
+ xml =<<-XML
117
+ <opt>
118
+ <user login="grep" fullname="Gary R Epstein" />
119
+ </opt>
120
+ XML
121
+
122
+ Hash.from_xml(xml)['opt']['user'].should be_an_instance_of(Hash)
123
+
124
+ hash = {
125
+ 'opt' => {
126
+ 'user' => {
127
+ 'login' => 'grep',
128
+ 'fullname' => 'Gary R Epstein'
129
+ }
130
+ }
131
+ }
132
+
133
+ Hash.from_xml(xml).should == hash
134
+ end
135
+
136
+ it "should typecast an integer" do
137
+ xml = "<tag type='integer'>10</tag>"
138
+ Hash.from_xml(xml)['tag'].should == 10
139
+ end
140
+
141
+ it "should typecast a true boolean" do
142
+ xml = "<tag type='boolean'>true</tag>"
143
+ Hash.from_xml(xml)['tag'].should be_true
144
+ end
145
+
146
+ it "should typecast a false boolean" do
147
+ ["false", "1", "0", "some word" ].each do |w|
148
+ Hash.from_xml("<tag type='boolean'>#{w}</tag>")['tag'].should be_false
149
+ end
150
+ end
151
+
152
+ it "should typecast a datetime" do
153
+ xml = "<tag type='datetime'>2007-12-31 10:32</tag>"
154
+ Hash.from_xml(xml)['tag'].should == Time.parse( '2007-12-31 10:32' ).utc
155
+ end
156
+
157
+ it "should typecast a date" do
158
+ xml = "<tag type='date'>2007-12-31</tag>"
159
+ Hash.from_xml(xml)['tag'].should == Date.parse('2007-12-31')
160
+ end
161
+
162
+ it "should unescape html entities" do
163
+ values = {
164
+ "<" => "&lt;",
165
+ ">" => "&gt;",
166
+ '"' => "&quot;",
167
+ "'" => "&apos;",
168
+ "&" => "&amp;"
169
+ }
170
+ values.each do |k,v|
171
+ xml = "<tag>Some content #{v}</tag>"
172
+ Hash.from_xml(xml)['tag'].should match(Regexp.new(k))
173
+ end
174
+ end
175
+
176
+ it "should undasherize keys as tags" do
177
+ xml = "<tag-1>Stuff</tag-1>"
178
+ Hash.from_xml(xml).keys.should include( 'tag_1' )
179
+ end
180
+
181
+ it "should undasherize keys as attributes" do
182
+ xml = "<tag1 attr-1='1'></tag1>"
183
+ Hash.from_xml(xml)['tag1'].keys.should include( 'attr_1')
184
+ end
185
+
186
+ it "should undasherize keys as tags and attributes" do
187
+ xml = "<tag-1 attr-1='1'></tag-1>"
188
+ Hash.from_xml(xml).keys.should include( 'tag_1' )
189
+ Hash.from_xml(xml)['tag_1'].keys.should include( 'attr_1')
190
+ end
191
+
192
+ it "should render nested content correctly" do
193
+ xml = "<root><tag1>Tag1 Content <em><strong>This is strong</strong></em></tag1></root>"
194
+ Hash.from_xml(xml)['root']['tag1'].should == "Tag1 Content <em><strong>This is strong</strong></em>"
195
+ end
196
+
197
+ it "should render nested content with split text nodes correctly" do
198
+ xml = "<root>Tag1 Content<em>Stuff</em> Hi There</root>"
199
+ Hash.from_xml(xml)['root'].should == "Tag1 Content<em>Stuff</em> Hi There"
200
+ end
201
+
202
+ it "should ignore attributes when a child is a text node" do
203
+ xml = "<root attr1='1'>Stuff</root>"
204
+ Hash.from_xml(xml).should == { "root" => "Stuff" }
205
+ end
206
+
207
+ it "should ignore attributes when any child is a text node" do
208
+ xml = "<root attr1='1'>Stuff <em>in italics</em></root>"
209
+ Hash.from_xml(xml).should == { "root" => "Stuff <em>in italics</em>" }
210
+ end
211
+
212
+ it "should correctly transform multiple children" do
213
+ xml = <<-XML
214
+ <user gender='m'>
215
+ <age type='integer'>35</age>
216
+ <name>Home Simpson</name>
217
+ <dob type='date'>1988-01-01</dob>
218
+ <joined-at type='datetime'>2000-04-28 23:01</joined-at>
219
+ <is-cool type='boolean'>true</is-cool>
220
+ </user>
221
+ XML
222
+
223
+ hash = {
224
+ "user" => {
225
+ "gender" => "m",
226
+ "age" => 35,
227
+ "name" => "Home Simpson",
228
+ "dob" => Date.parse('1988-01-01'),
229
+ "joined_at" => Time.parse("2000-04-28 23:01"),
230
+ "is_cool" => true
231
+ }
232
+ }
233
+
234
+ Hash.from_xml(xml).should == hash
235
+ end
236
+ end
237
+
238
+ describe Hash, 'to_params' do
239
+ before do
240
+ @hash = { :name => 'Bob', :address => { :street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222'] } }
241
+ end
242
+
243
+ it 'should convert correctly into query parameters' do
244
+ @hash.to_params.split('&').sort.should ==
245
+ 'name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave.'.split('&').sort
246
+ end
247
+
248
+ it 'should not leave a trailing &' do
249
+ @hash.to_params.should_not match(/&$/)
250
+ end
251
+ end