merb 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +21 -14
- data/Rakefile +157 -108
- data/SVN_REVISION +1 -0
- data/app_generators/merb/templates/Rakefile +20 -4
- data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +1 -1
- data/app_generators/merb/templates/config/boot.rb +1 -1
- data/app_generators/merb/templates/config/dependencies.rb +3 -3
- data/app_generators/merb/templates/config/merb.yml +5 -0
- data/app_generators/merb/templates/config/merb_init.rb +3 -3
- data/app_generators/merb/templates/script/destroy +3 -0
- data/app_generators/merb/templates/script/generate +1 -1
- data/app_generators/merb/templates/spec/spec_helper.rb +2 -2
- data/app_generators/merb/templates/test/test_helper.rb +1 -1
- data/app_generators/merb_plugin/merb_plugin_generator.rb +4 -0
- data/bin/merb +1 -3
- data/lib/merb.rb +144 -76
- data/lib/merb/abstract_controller.rb +6 -5
- data/lib/merb/assets.rb +119 -0
- data/lib/merb/boot_loader.rb +217 -0
- data/lib/merb/caching.rb +1 -1
- data/lib/merb/caching/action_cache.rb +1 -1
- data/lib/merb/caching/fragment_cache.rb +1 -1
- data/lib/merb/caching/store/file_cache.rb +1 -1
- data/lib/merb/config.rb +290 -0
- data/lib/merb/controller.rb +5 -5
- data/lib/merb/core_ext/get_args.rb +1 -0
- data/lib/merb/core_ext/hash.rb +182 -169
- data/lib/merb/core_ext/kernel.rb +57 -26
- data/lib/merb/dispatcher.rb +6 -6
- data/lib/merb/drb_server.rb +1 -1
- data/lib/merb/generators/merb_generator_helpers.rb +7 -6
- data/lib/merb/logger.rb +1 -1
- data/lib/merb/mail_controller.rb +3 -4
- data/lib/merb/mailer.rb +2 -2
- data/lib/merb/mixins/basic_authentication.rb +2 -2
- data/lib/merb/mixins/controller.rb +1 -1
- data/lib/merb/mixins/general_controller.rb +13 -20
- data/lib/merb/mixins/inline_partial.rb +32 -0
- data/lib/merb/mixins/render.rb +3 -3
- data/lib/merb/mixins/responder.rb +1 -1
- data/lib/merb/mixins/view_context.rb +159 -33
- data/lib/merb/mongrel_handler.rb +9 -9
- data/lib/merb/plugins.rb +1 -1
- data/lib/merb/request.rb +25 -1
- data/lib/merb/router.rb +264 -226
- data/lib/merb/server.rb +66 -560
- data/lib/merb/session/cookie_store.rb +14 -13
- data/lib/merb/session/mem_cache_session.rb +20 -10
- data/lib/merb/session/memory_session.rb +21 -11
- data/lib/merb/template.rb +2 -2
- data/lib/merb/template/erubis.rb +3 -33
- data/lib/merb/template/haml.rb +8 -3
- data/lib/merb/test/fake_request.rb +8 -3
- data/lib/merb/test/helper.rb +66 -22
- data/lib/merb/test/rspec.rb +9 -155
- data/lib/merb/test/rspec_matchers/controller_matchers.rb +117 -0
- data/lib/merb/test/rspec_matchers/markup_matchers.rb +98 -0
- data/lib/merb/upload_handler.rb +2 -1
- data/lib/merb/version.rb +38 -3
- data/lib/merb/view_context.rb +1 -2
- data/lib/tasks/merb.rake +11 -11
- data/merb_generators/part_controller/USAGE +5 -0
- data/merb_generators/part_controller/part_controller_generator.rb +27 -0
- data/merb_generators/part_controller/templates/controller.rb +8 -0
- data/merb_generators/part_controller/templates/helper.rb +5 -0
- data/merb_generators/part_controller/templates/index.html.erb +3 -0
- data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +1 -1
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/fixtures/controllers/dispatch_spec_controllers.rb +9 -1
- data/spec/fixtures/controllers/render_spec_controllers.rb +5 -5
- data/spec/fixtures/models/router_spec_models.rb +10 -0
- data/spec/merb/abstract_controller_spec.rb +2 -2
- data/spec/merb/assets_spec.rb +207 -0
- data/spec/merb/caching_spec.rb +2 -2
- data/spec/merb/controller_spec.rb +7 -2
- data/spec/merb/cookie_store_spec.rb +1 -1
- data/spec/merb/core_ext/class_spec.rb +97 -0
- data/spec/merb/core_ext/enumerable_spec.rb +27 -0
- data/spec/merb/core_ext/hash_spec.rb +251 -0
- data/spec/merb/core_ext/inflector_spec.rb +34 -0
- data/spec/merb/core_ext/kernel_spec.rb +25 -0
- data/spec/merb/core_ext/numeric_spec.rb +26 -0
- data/spec/merb/core_ext/object_spec.rb +47 -0
- data/spec/merb/core_ext/string_spec.rb +22 -0
- data/spec/merb/core_ext/symbol_spec.rb +7 -0
- data/spec/merb/dependency_spec.rb +22 -0
- data/spec/merb/dispatch_spec.rb +23 -12
- data/spec/merb/fake_request_spec.rb +8 -0
- data/spec/merb/generator_spec.rb +140 -21
- data/spec/merb/handler_spec.rb +5 -5
- data/spec/merb/mail_controller_spec.rb +3 -3
- data/spec/merb/render_spec.rb +1 -1
- data/spec/merb/responder_spec.rb +3 -3
- data/spec/merb/router_spec.rb +260 -191
- data/spec/merb/server_spec.rb +5 -5
- data/spec/merb/upload_handler_spec.rb +7 -0
- data/spec/merb/version_spec.rb +33 -0
- data/spec/merb/view_context_spec.rb +217 -59
- data/spec/spec_generator_helper.rb +15 -0
- data/spec/spec_helper.rb +5 -3
- data/spec/spec_helpers/url_shared_behaviour.rb +5 -7
- metadata +32 -7
- data/lib/merb/caching/store/memcache.rb +0 -20
- data/lib/merb/mixins/form_control.rb +0 -332
- data/lib/patch +0 -69
- data/spec/merb/core_ext_spec.rb +0 -464
- data/spec/merb/form_control_mixin_spec.rb +0 -431
@@ -2,6 +2,9 @@ module Merb
|
|
2
2
|
# The ViewContextMixin module provides a number of helper methods to views for
|
3
3
|
# linking to assets and other pages, dealing with JavaScript, and caching.
|
4
4
|
module ViewContextMixin
|
5
|
+
|
6
|
+
include Merb::Assets::AssetHelpers
|
7
|
+
|
5
8
|
# :section: Accessing Assets
|
6
9
|
# Merb provides views with convenience methods for links images and other assets.
|
7
10
|
|
@@ -52,8 +55,8 @@ module Merb
|
|
52
55
|
if img =~ %r{^https?://}
|
53
56
|
''
|
54
57
|
else
|
55
|
-
if Merb::
|
56
|
-
Merb::
|
58
|
+
if Merb::Config[:path_prefix]
|
59
|
+
Merb::Config[:path_prefix] + '/images/'
|
57
60
|
else
|
58
61
|
'/images/'
|
59
62
|
end
|
@@ -162,6 +165,77 @@ module Merb
|
|
162
165
|
#
|
163
166
|
# See each method's documentation for more information.
|
164
167
|
|
168
|
+
# :section: Bundling Asset Files
|
169
|
+
#
|
170
|
+
# The key to making a fast web application is to reduce both the amount of
|
171
|
+
# data transfered and the number of client-server interactions. While having
|
172
|
+
# many small, module Javascript or stylesheet files aids in the development
|
173
|
+
# process, your web application will benefit from bundling those assets in
|
174
|
+
# the production environment.
|
175
|
+
#
|
176
|
+
# An asset bundle is a set of asset files which are combined into a single
|
177
|
+
# file. This reduces the number of requests required to render a page, and
|
178
|
+
# can reduce the amount of data transfer required if you're using gzip
|
179
|
+
# encoding.
|
180
|
+
#
|
181
|
+
# Asset bundling is always enabled in production mode, and can be optionally
|
182
|
+
# enabled in all environments by setting the <tt>:bundle_assets</tt> value
|
183
|
+
# in <tt>config/merb.yml</tt> to +true+.
|
184
|
+
#
|
185
|
+
# ==== Examples
|
186
|
+
#
|
187
|
+
# In the development environment, this:
|
188
|
+
#
|
189
|
+
# js_include_tag :prototype, :lowpro, :bundle => true
|
190
|
+
#
|
191
|
+
# will produce two <script> elements. In the production mode, however, the
|
192
|
+
# two files will be concatenated in the order given into a single file,
|
193
|
+
# <tt>all.js</tt>, in the <tt>public/javascripts</tt> directory.
|
194
|
+
#
|
195
|
+
# To specify a different bundle name:
|
196
|
+
#
|
197
|
+
# css_include_tag :typography, :whitespace, :bundle => :base
|
198
|
+
# css_include_tag :header, :footer, :bundle => "content"
|
199
|
+
# css_include_tag :lightbox, :images, :bundle => "lb.css"
|
200
|
+
#
|
201
|
+
# (<tt>base.css</tt>, <tt>content.css</tt>, and <tt>lb.css</tt> will all be
|
202
|
+
# created in the <tt>public/stylesheets</tt> directory.)
|
203
|
+
#
|
204
|
+
# == Callbacks
|
205
|
+
#
|
206
|
+
# To use a Javascript or CSS compressor, like JSMin or YUI Compressor:
|
207
|
+
#
|
208
|
+
# Merb::Assets::JavascriptAssetBundler.add_callback do |filename|
|
209
|
+
# system("/usr/local/bin/yui-compress #{filename}")
|
210
|
+
# end
|
211
|
+
#
|
212
|
+
# Merb::Assets::StylesheetAssetBundler.add_callback do |filename|
|
213
|
+
# system("/usr/local/bin/css-min #{filename}")
|
214
|
+
# end
|
215
|
+
#
|
216
|
+
# These blocks will be run after a bundle is created.
|
217
|
+
#
|
218
|
+
# == Bundling Required Assets
|
219
|
+
#
|
220
|
+
# Combining the +require_css+ and +require_js+ helpers with bundling can be
|
221
|
+
# problematic. You may want to separate out the common assets for your
|
222
|
+
# application -- Javascript frameworks, common CSS, etc. -- and bundle those
|
223
|
+
# in a "base" bundle. Then, for each section of your site, bundle the
|
224
|
+
# required assets into a section-specific bundle.
|
225
|
+
#
|
226
|
+
# <b>N.B.: If you bundle an inconsistent set of assets with the same name,
|
227
|
+
# you will have inconsistent results. Be thorough and test often.</b>
|
228
|
+
#
|
229
|
+
# ==== Example
|
230
|
+
#
|
231
|
+
# In your application layout:
|
232
|
+
#
|
233
|
+
# js_include_tag :prototype, :lowpro, :bundle => :base
|
234
|
+
#
|
235
|
+
# In your controller layout:
|
236
|
+
#
|
237
|
+
# require_js :bundle => :posts
|
238
|
+
|
165
239
|
# The require_js method can be used to require any JavaScript
|
166
240
|
# file anywhere in your templates. Regardless of how many times
|
167
241
|
# a single script is included with require_js, Merb will only include
|
@@ -182,7 +256,7 @@ module Merb
|
|
182
256
|
@required_js |= js
|
183
257
|
end
|
184
258
|
|
185
|
-
# The
|
259
|
+
# The require_css method can be used to require any CSS
|
186
260
|
# file anywhere in your templates. Regardless of how many times
|
187
261
|
# a single stylesheet is included with require_css, Merb will only include
|
188
262
|
# it once in the header.
|
@@ -204,7 +278,13 @@ module Merb
|
|
204
278
|
|
205
279
|
# A method used in the layout of an application to create +<script>+ tags to include JavaScripts required in
|
206
280
|
# in templates and subtemplates using require_js.
|
207
|
-
#
|
281
|
+
#
|
282
|
+
# ==== Options
|
283
|
+
# bundle:: The name of the bundle the scripts should be combined into.
|
284
|
+
# If +nil+ or +false+, the bundle is not created. If +true+, a
|
285
|
+
# bundle named <tt>all.js</tt> is created. Otherwise,
|
286
|
+
# <tt>:bundle</tt> is treated as an asset name.
|
287
|
+
#
|
208
288
|
# ==== Examples
|
209
289
|
# # my_action.herb has a call to require_js 'jquery'
|
210
290
|
# # File: layout/application.html.erb
|
@@ -218,14 +298,20 @@ module Merb
|
|
218
298
|
# # <script src="/javascripts/effects.js" type="text/javascript"></script>
|
219
299
|
# # <script src="/javascripts/validation.js" type="text/javascript"></script>
|
220
300
|
#
|
221
|
-
def include_required_js
|
301
|
+
def include_required_js(options = {})
|
222
302
|
return '' if @required_js.nil?
|
223
|
-
js_include_tag(
|
303
|
+
js_include_tag(*(@required_js + [options]))
|
224
304
|
end
|
225
305
|
|
226
306
|
# A method used in the layout of an application to create +<link>+ tags for CSS stylesheets required in
|
227
307
|
# in templates and subtemplates using require_css.
|
228
|
-
#
|
308
|
+
#
|
309
|
+
# ==== Options
|
310
|
+
# bundle:: The name of the bundle the stylesheets should be combined into.
|
311
|
+
# If +nil+ or +false+, the bundle is not created. If +true+, a
|
312
|
+
# bundle named <tt>all.css</tt> is created. Otherwise,
|
313
|
+
# <tt>:bundle</tt> is treated as an asset name.
|
314
|
+
#
|
229
315
|
# ==== Examples
|
230
316
|
# # my_action.herb has a call to require_css 'style'
|
231
317
|
# # File: layout/application.html.erb
|
@@ -238,15 +324,21 @@ module Merb
|
|
238
324
|
# # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
|
239
325
|
# # <link href="/stylesheets/ie-specific.css" media="all" rel="Stylesheet" type="text/css"/>
|
240
326
|
#
|
241
|
-
def include_required_css
|
327
|
+
def include_required_css(options = {})
|
242
328
|
return '' if @required_css.nil?
|
243
|
-
css_include_tag(
|
329
|
+
css_include_tag(*(@required_css + [options]))
|
244
330
|
end
|
245
331
|
|
246
332
|
# The js_include_tag method will create a JavaScript
|
247
333
|
# +<include>+ tag for each script named in the arguments, appending
|
248
334
|
# '.js' if it is left out of the call.
|
249
|
-
#
|
335
|
+
#
|
336
|
+
# ==== Options
|
337
|
+
# bundle:: The name of the bundle the scripts should be combined into.
|
338
|
+
# If +nil+ or +false+, the bundle is not created. If +true+, a
|
339
|
+
# bundle named <tt>all.js</tt> is created. Otherwise,
|
340
|
+
# <tt>:bundle</tt> is treated as an asset name.
|
341
|
+
#
|
250
342
|
# ==== Examples
|
251
343
|
# js_include_tag 'jquery'
|
252
344
|
# # => <script src="/javascripts/jquery.js" type="text/javascript"></script>
|
@@ -263,46 +355,80 @@ module Merb
|
|
263
355
|
# # <script src="/javascripts/validation.js" type="text/javascript"></script>
|
264
356
|
#
|
265
357
|
def js_include_tag(*scripts)
|
358
|
+
options = scripts.last.is_a?(Hash) ? scripts.pop : {}
|
266
359
|
return nil if scripts.empty?
|
267
|
-
|
268
|
-
scripts.
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
360
|
+
|
361
|
+
if (bundle_name = options[:bundle]) && Merb::Assets.bundle? && scripts.size > 1
|
362
|
+
bundler = Merb::Assets::JavascriptAssetBundler.new(bundle_name, *scripts)
|
363
|
+
bundled_asset = bundler.bundle!
|
364
|
+
return js_include_tag(bundled_asset)
|
365
|
+
end
|
366
|
+
|
367
|
+
tags = ""
|
368
|
+
|
369
|
+
for script in scripts
|
370
|
+
attrs = {
|
371
|
+
:src => asset_path(:javascript, script),
|
372
|
+
:type => "text/javascript"
|
373
|
+
}
|
374
|
+
tags << %Q{<script #{attrs.to_xml_attributes}>//</script>}
|
273
375
|
end
|
274
|
-
|
376
|
+
|
377
|
+
return tags
|
275
378
|
end
|
276
379
|
|
277
380
|
# The css_include_tag method will create a CSS stylesheet
|
278
381
|
# +<link>+ tag for each stylesheet named in the arguments, appending
|
279
382
|
# '.css' if it is left out of the call.
|
383
|
+
#
|
384
|
+
# ==== Options
|
385
|
+
# bundle:: The name of the bundle the stylesheets should be combined into.
|
386
|
+
# If +nil+ or +false+, the bundle is not created. If +true+, a
|
387
|
+
# bundle named <tt>all.css</tt> is created. Otherwise,
|
388
|
+
# <tt>:bundle</tt> is treated as an asset name.
|
389
|
+
# media:: The media attribute for the generated link element. Defaults
|
390
|
+
# to <tt>:all</tt>.
|
280
391
|
#
|
281
392
|
# ==== Examples
|
282
393
|
# css_include_tag 'style'
|
283
|
-
# # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
|
394
|
+
# # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
|
284
395
|
#
|
285
396
|
# css_include_tag 'style.css', 'layout'
|
286
|
-
# # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
|
287
|
-
# # <link href="/stylesheets/layout.css" media="all" rel="Stylesheet" type="text/css"/>
|
397
|
+
# # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
|
398
|
+
# # <link href="/stylesheets/layout.css" media="all" rel="Stylesheet" type="text/css" />
|
288
399
|
#
|
289
400
|
# css_include_tag :menu
|
290
|
-
# # => <link href="/stylesheets/menu.css" media="all" rel="Stylesheet" type="text/css"/>
|
401
|
+
# # => <link href="/stylesheets/menu.css" media="all" rel="Stylesheet" type="text/css" />
|
291
402
|
#
|
292
403
|
# css_include_tag :style, :screen
|
293
|
-
# # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css"/>
|
294
|
-
# # <link href="/stylesheets/screen.css" media="all" rel="Stylesheet" type="text/css"/>
|
295
|
-
#
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
404
|
+
# # => <link href="/stylesheets/style.css" media="all" rel="Stylesheet" type="text/css" />
|
405
|
+
# # <link href="/stylesheets/screen.css" media="all" rel="Stylesheet" type="text/css" />
|
406
|
+
#
|
407
|
+
# css_include_tag :style, :media => :print
|
408
|
+
# # => <link href="/stylesheets/style.css" media="print" rel="Stylesheet" type="text/css" />
|
409
|
+
def css_include_tag(*stylesheets)
|
410
|
+
options = stylesheets.last.is_a?(Hash) ? stylesheets.pop : {}
|
411
|
+
return nil if stylesheets.empty?
|
412
|
+
|
413
|
+
if (bundle_name = options[:bundle]) && Merb::Assets.bundle? && stylesheets.size > 1
|
414
|
+
bundler = Merb::Assets::StylesheetAssetBundler.new(bundle_name, *stylesheets)
|
415
|
+
bundled_asset = bundler.bundle!
|
416
|
+
return css_include_tag(bundled_asset)
|
304
417
|
end
|
305
|
-
|
418
|
+
|
419
|
+
tags = ""
|
420
|
+
|
421
|
+
for stylesheet in stylesheets
|
422
|
+
attrs = {
|
423
|
+
:href => asset_path(:stylesheet, stylesheet),
|
424
|
+
:type => "text/css",
|
425
|
+
:rel => "Stylesheet",
|
426
|
+
:media => options[:media] || :all
|
427
|
+
}
|
428
|
+
tags << %Q{<link #{attrs.to_xml_attributes} />}
|
429
|
+
end
|
430
|
+
|
431
|
+
return tags
|
306
432
|
end
|
307
433
|
|
308
434
|
# :section: Caching
|
data/lib/merb/mongrel_handler.rb
CHANGED
@@ -49,14 +49,14 @@ class MerbHandler < Mongrel::HttpHandler
|
|
49
49
|
start = Time.now
|
50
50
|
benchmarks = {}
|
51
51
|
|
52
|
-
|
52
|
+
Merb.logger.info("\nRequest: REQUEST_URI: #{
|
53
53
|
request.params[Mongrel::Const::REQUEST_URI]} (#{Time.now.strftime("%Y-%m-%d %H:%M:%S")})")
|
54
54
|
|
55
55
|
# Truncate the request URI if there's a path prefix so that an app can be
|
56
56
|
# hosted inside a subdirectory, for example.
|
57
57
|
if @@path_prefix
|
58
58
|
if request.params[Mongrel::Const::PATH_INFO] =~ @@path_prefix
|
59
|
-
|
59
|
+
Merb.logger.info("Path prefix #{@@path_prefix.inspect} removed from PATH_INFO and REQUEST_URI.")
|
60
60
|
request.params[Mongrel::Const::PATH_INFO].sub!(@@path_prefix, '')
|
61
61
|
request.params[Mongrel::Const::REQUEST_URI].sub!(@@path_prefix, '')
|
62
62
|
path_info = request.params[Mongrel::Const::PATH_INFO]
|
@@ -75,11 +75,11 @@ class MerbHandler < Mongrel::HttpHandler
|
|
75
75
|
|
76
76
|
if get_or_head && @files.can_serve(path_info)
|
77
77
|
# File exists as-is so serve it up
|
78
|
-
|
78
|
+
Merb.logger.info("Serving static file: #{path_info}")
|
79
79
|
@files.process(request,response)
|
80
80
|
elsif get_or_head && @files.can_serve(page_cached)
|
81
81
|
# Possible cached page, serve it up
|
82
|
-
|
82
|
+
Merb.logger.info("Serving static file: #{page_cached}")
|
83
83
|
request.params[Mongrel::Const::PATH_INFO] = page_cached
|
84
84
|
@files.process(request,response)
|
85
85
|
else
|
@@ -89,7 +89,7 @@ class MerbHandler < Mongrel::HttpHandler
|
|
89
89
|
benchmarks[:controller] = controller.class.to_s
|
90
90
|
benchmarks[:action] = action
|
91
91
|
|
92
|
-
|
92
|
+
Merb.logger.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{benchmarks[:setup_time]} seconds")
|
93
93
|
|
94
94
|
sendfile, clength = nil
|
95
95
|
response.status = controller.status
|
@@ -111,7 +111,7 @@ class MerbHandler < Mongrel::HttpHandler
|
|
111
111
|
if @mongrel_x_sendfile
|
112
112
|
# we want to emulate X-Sendfile header internally in mongrel
|
113
113
|
benchmarks[:sendfile_time] = Time.now - start
|
114
|
-
|
114
|
+
Merb.logger.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{
|
115
115
|
benchmarks[:sendfile_time]} seconds")
|
116
116
|
file_status = File.stat(sendfile)
|
117
117
|
response.status = 200
|
@@ -152,7 +152,7 @@ class MerbHandler < Mongrel::HttpHandler
|
|
152
152
|
total_request_time = Time.now - start
|
153
153
|
benchmarks[:total_request_time] = total_request_time
|
154
154
|
|
155
|
-
|
155
|
+
Merb.logger.info("Request Times: #{benchmarks.inspect}\nResponse status: #{response.status}\nComplete Request took: #{total_request_time} seconds, #{1.0/total_request_time} Requests/Second\n\n")
|
156
156
|
end
|
157
157
|
rescue Object => e
|
158
158
|
# if an exception is raised here then something is
|
@@ -161,8 +161,8 @@ class MerbHandler < Mongrel::HttpHandler
|
|
161
161
|
response.send_status(500)
|
162
162
|
response.send_header
|
163
163
|
response.write("500 Internal Server Error")
|
164
|
-
|
164
|
+
Merb.logger.error(Merb.exception(e))
|
165
165
|
ensure
|
166
|
-
|
166
|
+
Merb.logger.flush
|
167
167
|
end
|
168
168
|
end
|
data/lib/merb/plugins.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Merb
|
2
2
|
module Plugins
|
3
3
|
def self.config
|
4
|
-
@config ||= File.exists?(
|
4
|
+
@config ||= File.exists?(Merb.root / "config" / "plugins.yml") ? YAML.load(File.read(Merb.root / "config" / "plugins.yml")) || {} : {}
|
5
5
|
end
|
6
6
|
|
7
7
|
@rakefiles = []
|
data/lib/merb/request.rb
CHANGED
@@ -328,6 +328,30 @@ module Merb
|
|
328
328
|
end
|
329
329
|
|
330
330
|
class << self
|
331
|
+
# Escapes +s+ for use in a URL.
|
332
|
+
#
|
333
|
+
# ==== Parameter
|
334
|
+
#
|
335
|
+
# +s+ - String to URL escape.
|
336
|
+
#
|
337
|
+
def escape(s)
|
338
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
339
|
+
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
340
|
+
}.tr(' ', '+')
|
341
|
+
end
|
342
|
+
|
343
|
+
# Unescapes a string (i.e., reverse URL escaping).
|
344
|
+
#
|
345
|
+
# ==== Parameter
|
346
|
+
#
|
347
|
+
# +s+ - String to unescape.
|
348
|
+
#
|
349
|
+
def unescape(s)
|
350
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
351
|
+
[$1.delete('%')].pack('H*')
|
352
|
+
}
|
353
|
+
end
|
354
|
+
|
331
355
|
# parses a query string or the payload of a POST
|
332
356
|
# request into the params hash. So for example:
|
333
357
|
# /foo?bar=nik&post[title]=heya&post[body]=whatever
|
@@ -335,7 +359,7 @@ module Merb
|
|
335
359
|
# {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}}
|
336
360
|
def query_parse(qs, d = '&;')
|
337
361
|
(qs||'').split(/[#{d}] */n).inject({}) { |h,p|
|
338
|
-
normalize_params(h, *
|
362
|
+
normalize_params(h, *unescape(p).split('=',2))
|
339
363
|
}.to_mash
|
340
364
|
end
|
341
365
|
|
data/lib/merb/router.rb
CHANGED
@@ -1,31 +1,30 @@
|
|
1
1
|
module Merb
|
2
|
-
|
3
2
|
class Router
|
4
3
|
SEGMENT_REGEXP = /(:([a-z_][a-z0-9_]*|:))/
|
5
4
|
SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
|
6
5
|
JUST_BRACKETS = /\[(\d+)\]/
|
7
6
|
PARENTHETICAL_SEGMENT_STRING = "([^\/.,;?]+)".freeze
|
8
|
-
|
7
|
+
|
9
8
|
@@named_routes = {}
|
10
9
|
@@routes = []
|
11
10
|
cattr_accessor :routes, :named_routes
|
12
|
-
|
11
|
+
|
13
12
|
class << self
|
14
13
|
def append(&block)
|
15
14
|
prepare(@@routes, [], &block)
|
16
15
|
end
|
17
|
-
|
16
|
+
|
18
17
|
def prepend(&block)
|
19
18
|
prepare([], @@routes, &block)
|
20
19
|
end
|
21
|
-
|
20
|
+
|
22
21
|
def prepare(first = [], last = [], &block)
|
23
22
|
@@routes = []
|
24
|
-
yield Behavior.new({}, {:action => 'index'}) # defaults
|
23
|
+
yield Behavior.new({}, { :action => 'index' }) # defaults
|
25
24
|
@@routes = first + @@routes + last
|
26
25
|
compile
|
27
26
|
end
|
28
|
-
|
27
|
+
|
29
28
|
def compiled_statement
|
30
29
|
@@compiled_statement = "lambda { |request, params|\n"
|
31
30
|
@@compiled_statement << " cached_path = request.path\n cached_method = request.method.to_s\n "
|
@@ -35,31 +34,37 @@ module Merb
|
|
35
34
|
@@compiled_statement << " end\n"
|
36
35
|
@@compiled_statement << "}"
|
37
36
|
end
|
38
|
-
|
37
|
+
|
39
38
|
def compile
|
40
39
|
meta_def(:match, &eval(compiled_statement))
|
41
40
|
end
|
42
|
-
|
41
|
+
|
43
42
|
def generate(name, params = {}, fallback = {})
|
44
|
-
|
45
|
-
@@named_routes
|
43
|
+
name = name.to_sym
|
44
|
+
unless @@named_routes.key? name
|
45
|
+
raise "Named route not found: #{name}"
|
46
|
+
else
|
47
|
+
@@named_routes[name].generate(params, fallback)
|
48
|
+
end
|
46
49
|
end
|
47
50
|
end # self
|
48
|
-
|
51
|
+
|
49
52
|
# Cache procs for future reference in eval statement
|
50
53
|
class CachedProc
|
51
54
|
@@index = 0
|
52
55
|
@@list = []
|
53
|
-
|
56
|
+
|
54
57
|
attr_accessor :cache, :index
|
55
|
-
|
58
|
+
|
56
59
|
def initialize(cache)
|
57
60
|
@cache, @index = cache, CachedProc.register(self)
|
58
61
|
end
|
59
|
-
|
62
|
+
|
60
63
|
# Make each CachedProc object embeddable within a string
|
61
|
-
def to_s
|
62
|
-
|
64
|
+
def to_s
|
65
|
+
"CachedProc[#{@index}].cache"
|
66
|
+
end
|
67
|
+
|
63
68
|
class << self
|
64
69
|
def register(cached_code)
|
65
70
|
CachedProc[@@index] = cached_code
|
@@ -70,11 +75,11 @@ module Merb
|
|
70
75
|
def [](index) @@list[index] end
|
71
76
|
end
|
72
77
|
end # CachedProc
|
73
|
-
|
78
|
+
|
74
79
|
class Route
|
75
80
|
attr_reader :conditions, :conditional_block
|
76
81
|
attr_reader :params, :behavior, :segments, :index, :symbol
|
77
|
-
|
82
|
+
|
78
83
|
def initialize(conditions, params, behavior = nil, &conditional_block)
|
79
84
|
@conditions, @params, @behavior = conditions, params, behavior
|
80
85
|
@conditional_block = conditional_block
|
@@ -82,31 +87,25 @@ module Merb
|
|
82
87
|
@segments = segments_from_path(path)
|
83
88
|
end
|
84
89
|
end
|
85
|
-
|
90
|
+
|
86
91
|
def to_s
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
r << ":#{s}"
|
91
|
-
else
|
92
|
-
r << s
|
93
|
-
end
|
94
|
-
}
|
95
|
-
r
|
92
|
+
segments.inject('') do |str,seg|
|
93
|
+
str << (seg.is_a?(Symbol) ? ":#{seg}" : seg)
|
94
|
+
end
|
96
95
|
end
|
97
|
-
|
96
|
+
|
98
97
|
# Registers itself in the Router.routes array
|
99
98
|
def register
|
100
99
|
@index = Router.routes.size
|
101
100
|
Router.routes << self
|
102
101
|
self
|
103
102
|
end
|
104
|
-
|
103
|
+
|
105
104
|
# Get the symbols out of the segments array
|
106
105
|
def symbol_segments
|
107
106
|
segments.select{ |s| s.is_a?(Symbol) }
|
108
107
|
end
|
109
|
-
|
108
|
+
|
110
109
|
# Turn a path into string and symbol segments so it can be reconstructed, as in the
|
111
110
|
# case of a named route.
|
112
111
|
def segments_from_path(path)
|
@@ -121,17 +120,17 @@ module Merb
|
|
121
120
|
segments << strip[path] unless path.empty?
|
122
121
|
segments
|
123
122
|
end
|
124
|
-
|
123
|
+
|
125
124
|
# Name this route
|
126
125
|
def name(symbol = nil)
|
127
|
-
@symbol = symbol
|
126
|
+
raise ArgumentError unless (@symbol = symbol).is_a?(Symbol)
|
128
127
|
Router.named_routes[@symbol] = self
|
129
128
|
end
|
130
|
-
|
129
|
+
|
131
130
|
def regexp?
|
132
|
-
behavior.regexp? || behavior.send(:ancestors).any?{ |a| a.regexp? }
|
131
|
+
behavior.regexp? || behavior.send(:ancestors).any? { |a| a.regexp? }
|
133
132
|
end
|
134
|
-
|
133
|
+
|
135
134
|
# Given a hash of +params+, returns a string using the stored route segments
|
136
135
|
# for reconstruction of the URL.
|
137
136
|
def generate(params = {}, fallback = {})
|
@@ -141,7 +140,9 @@ module Merb
|
|
141
140
|
if params.is_a? Hash
|
142
141
|
params[segment] || fallback[segment]
|
143
142
|
else
|
144
|
-
if params.respond_to?(
|
143
|
+
if segment == :id && params.respond_to?(:to_param)
|
144
|
+
params.to_param
|
145
|
+
elsif params.respond_to?(segment)
|
145
146
|
params.send(segment)
|
146
147
|
else
|
147
148
|
fallback[segment]
|
@@ -155,7 +156,7 @@ module Merb
|
|
155
156
|
(value.respond_to?(:to_param) ? value.to_param : value).to_s
|
156
157
|
end.join
|
157
158
|
end
|
158
|
-
|
159
|
+
|
159
160
|
def if_conditions(params_as_string)
|
160
161
|
cond = []
|
161
162
|
condition_string = proc do |key, value, regexp_string|
|
@@ -169,7 +170,7 @@ module Merb
|
|
169
170
|
" (#{value.inspect} =~ #{regexp_string}" + captures + ")"
|
170
171
|
end
|
171
172
|
@conditions.each_pair do |key, value|
|
172
|
-
|
173
|
+
|
173
174
|
# Note: =~ is slightly faster than .match
|
174
175
|
cond << case key
|
175
176
|
when :path then condition_string[key, value, "cached_path"]
|
@@ -184,10 +185,10 @@ module Merb
|
|
184
185
|
end
|
185
186
|
cond
|
186
187
|
end
|
187
|
-
|
188
|
+
|
188
189
|
def compile(first = false)
|
189
190
|
code = ""
|
190
|
-
default_params = {:action => "index"}
|
191
|
+
default_params = { :action => "index" }
|
191
192
|
get_value = proc do |key|
|
192
193
|
if default_params.has_key?(key) && params[key][0] != ?"
|
193
194
|
"#{params[key]} || \"#{default_params[key]}\""
|
@@ -195,7 +196,7 @@ module Merb
|
|
195
196
|
"#{params[key]}"
|
196
197
|
end
|
197
198
|
end
|
198
|
-
params_as_string = params.keys.map{|k| "#{k.inspect} => #{get_value[k]}"}.join(
|
199
|
+
params_as_string = params.keys.map { |k| "#{k.inspect} => #{get_value[k]}" }.join(', ')
|
199
200
|
code << " els" unless first
|
200
201
|
code << "if # #{@behavior.merged_original_conditions.inspect}\n "
|
201
202
|
code << if_conditions(params_as_string).join(" &&\n ") << "\n"
|
@@ -206,7 +207,7 @@ module Merb
|
|
206
207
|
code << " [#{@index.inspect}, {#{params_as_string}}]\n"
|
207
208
|
end
|
208
209
|
end
|
209
|
-
|
210
|
+
|
210
211
|
def behavior_trace
|
211
212
|
if @behavior
|
212
213
|
puts @behavior.send(:ancestors).reverse.map{|a| a.inspect}.join("\n"); puts @behavior.inspect; puts
|
@@ -215,30 +216,65 @@ module Merb
|
|
215
216
|
end
|
216
217
|
end
|
217
218
|
end # Route
|
218
|
-
|
219
|
-
# The Behavior class is an interim route-building class that ties
|
220
|
-
# output parameters, +params+.
|
219
|
+
|
220
|
+
# The Behavior class is an interim route-building class that ties
|
221
|
+
# pattern-matching +conditions+ to output parameters, +params+.
|
221
222
|
class Behavior
|
222
223
|
attr_reader :placeholders, :conditions, :params
|
223
224
|
attr_accessor :parent
|
224
|
-
|
225
|
+
|
226
|
+
class << self
|
227
|
+
# Count the number of open parentheses in +string+, up to and including +pos+
|
228
|
+
def count_parens_up_to(string, pos)
|
229
|
+
string[0..pos].gsub(/[^\(]/, '').size
|
230
|
+
end
|
231
|
+
|
232
|
+
# Concatenate strings and remove regexp end caps
|
233
|
+
def concat_without_endcaps(string1, string2)
|
234
|
+
return nil if !string1 and !string2
|
235
|
+
return string1 if string2.nil?
|
236
|
+
return string2 if string1.nil?
|
237
|
+
s1 = string1[-1] == ?$ ? string1[0..-2] : string1
|
238
|
+
s2 = string2[0] == ?^ ? string2[1..-1] : string2
|
239
|
+
s1 + s2
|
240
|
+
end
|
241
|
+
|
242
|
+
# Join an array's elements into a string using " + " as a joiner, and
|
243
|
+
# surround string elements in quotes.
|
244
|
+
def array_to_code(arr)
|
245
|
+
code = ''
|
246
|
+
arr.each_with_index do |part, i|
|
247
|
+
code << ' + ' if i > 0
|
248
|
+
case part
|
249
|
+
when Symbol
|
250
|
+
code << part.to_s
|
251
|
+
when String
|
252
|
+
code << %{"#{part}"}
|
253
|
+
else
|
254
|
+
raise "Don't know how to compile array part: #{part.class} [#{i}]"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
code
|
258
|
+
end
|
259
|
+
end # class << self
|
260
|
+
|
225
261
|
def initialize(conditions = {}, params = {}, parent = nil)
|
262
|
+
# Must wait until after deducing placeholders to set @params !
|
226
263
|
@conditions, @params, @parent = conditions, {}, parent
|
227
264
|
@placeholders = {}
|
228
265
|
stringify_conditions
|
229
266
|
copy_original_conditions
|
230
267
|
deduce_placeholders
|
231
|
-
|
232
|
-
@params.merge!(params)
|
268
|
+
@params.merge! params
|
233
269
|
end
|
234
|
-
|
270
|
+
|
235
271
|
def add(path, params = {})
|
236
272
|
match(path).to(params)
|
237
273
|
end
|
238
|
-
|
274
|
+
|
239
275
|
# Matches a +path+ and any number of optional request methods as conditions of a route.
|
240
276
|
# Alternatively, +path+ can be a hash of conditions, in which case +conditions+ is ignored.
|
241
|
-
# Yields
|
277
|
+
# Yields a new instance so that sub-matching may occur.
|
242
278
|
def match(path = '', conditions = {}, &block)
|
243
279
|
if path.is_a? Hash
|
244
280
|
conditions = path
|
@@ -247,28 +283,32 @@ module Merb
|
|
247
283
|
end
|
248
284
|
match_without_path(conditions, &block)
|
249
285
|
end
|
250
|
-
|
286
|
+
|
251
287
|
def match_without_path(conditions = {})
|
252
288
|
new_behavior = self.class.new(conditions, {}, self)
|
253
289
|
conditions.delete :path if ['', '^$'].include?(conditions[:path])
|
254
290
|
yield new_behavior if block_given?
|
255
291
|
new_behavior
|
256
292
|
end
|
257
|
-
|
293
|
+
|
258
294
|
def to_route(params = {}, &conditional_block)
|
259
|
-
@params.merge!
|
260
|
-
Route.new
|
295
|
+
@params.merge! params
|
296
|
+
Route.new compiled_conditions, compiled_params, self, &conditional_block
|
261
297
|
end
|
262
|
-
|
298
|
+
|
263
299
|
# Creates a Route from one or more Behavior objects, unless a +block+ is passed in.
|
264
300
|
# If a block is passed in, a Behavior object is yielded and further .to operations
|
265
301
|
# may be called in the block. For example:
|
302
|
+
#
|
266
303
|
# r.match('/:controller/:id).to(:action => 'show')
|
304
|
+
#
|
267
305
|
# vs.
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
306
|
+
#
|
307
|
+
# r.to :controller => 'simple' do |s|
|
308
|
+
# s.match('/test').to(:action => 'index')
|
309
|
+
# s.match('/other').to(:action => 'other')
|
271
310
|
# end
|
311
|
+
#
|
272
312
|
def to(params = {}, &block)
|
273
313
|
if block_given?
|
274
314
|
new_behavior = self.class.new({}, params, self)
|
@@ -278,129 +318,148 @@ module Merb
|
|
278
318
|
to_route(params).register
|
279
319
|
end
|
280
320
|
end
|
281
|
-
|
282
|
-
# Takes a block and stores it for defered conditional routes.
|
283
|
-
# +request+ object and the +params+ hash as parameters
|
284
|
-
#
|
321
|
+
|
322
|
+
# Takes a block and stores it for defered conditional routes.
|
323
|
+
# The block takes the +request+ object and the +params+ hash as parameters
|
324
|
+
# and should return a hash of params.
|
325
|
+
#
|
285
326
|
# r.defer_to do |request, params|
|
286
|
-
# params.merge
|
327
|
+
# params.merge :controller => 'here', :action => 'there'
|
287
328
|
# end
|
329
|
+
#
|
288
330
|
def defer_to(params = {}, &conditional_block)
|
289
331
|
Router.routes << (route = to_route(params, &conditional_block))
|
290
332
|
route
|
291
333
|
end
|
292
|
-
|
334
|
+
|
293
335
|
def default_routes(params = {}, &block)
|
294
|
-
match(%r
|
336
|
+
match(%r{/:controller(/:action(/:id)?)?(\.:format)?}).to(params, &block)
|
295
337
|
end
|
296
|
-
|
297
|
-
def
|
298
|
-
|
338
|
+
|
339
|
+
def namespace(name_or_path, &block)
|
340
|
+
yield self.class.new(:namespace => name_or_path.to_s)
|
299
341
|
end
|
300
|
-
|
342
|
+
|
301
343
|
def resources(name, options = {})
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
344
|
+
namespace = options[:namespace] || merged_params[:namespace] || conditions[:namespace]
|
345
|
+
|
346
|
+
match_path = namespace ? "/#{namespace}/#{name}" : "/#{name}"
|
347
|
+
|
348
|
+
next_level = match match_path
|
307
349
|
|
308
|
-
|
350
|
+
name_prefix = options.delete :name_prefix
|
309
351
|
|
310
|
-
if
|
311
|
-
|
352
|
+
if name_prefix.nil? && !namespace.nil?
|
353
|
+
name_prefix = namespace_to_name_prefix namespace
|
312
354
|
end
|
313
355
|
|
314
|
-
name_prefix = options.delete(:name_prefix)
|
315
|
-
|
316
|
-
|
317
|
-
route_plural_name = "#{name_prefix}#{name}"
|
318
|
-
route_singular_name = "#{name_prefix}#{singular}"
|
319
|
-
|
320
|
-
member = options.delete(:member)
|
321
|
-
collection = options.delete(:collection)
|
322
|
-
|
323
|
-
# Add optional member actions
|
324
|
-
|
325
|
-
member.each_pair do |action, methods|
|
326
|
-
conditions = {:path => %r[^/:id[/;]#{action}$], :method => /^(#{[methods].flatten.join("|")})$/}
|
327
|
-
behaviors << Behavior.new(conditions, {:action => action.to_s}, next_level)
|
328
|
-
next_level.match("/:id/#{action}").
|
329
|
-
to_route.name(:"#{action}_#{route_singular_name}")
|
330
|
-
end if member
|
331
|
-
|
332
|
-
# Add optional collection actions
|
333
|
-
collection.each_pair do |action, methods|
|
334
|
-
conditions = {:path => %r[^[/;]#{action}$], :method => /^(#{[methods].flatten.join("|")})$/}
|
335
|
-
behaviors << Behavior.new(conditions, {:action => action.to_s}, next_level)
|
336
|
-
next_level.match("/#{action}").to_route.name(:"#{action}_#{route_plural_name}")
|
337
|
-
end if collection
|
338
|
-
|
339
356
|
options[:controller] ||= merged_params[:controller] || name.to_s
|
357
|
+
|
358
|
+
singular = name.to_s.singularize
|
359
|
+
|
360
|
+
route_plural_name = "#{name_prefix}#{name}"
|
361
|
+
route_singular_name = "#{name_prefix}#{singular}"
|
362
|
+
|
363
|
+
behaviors = []
|
364
|
+
|
365
|
+
if member = options.delete(:member)
|
366
|
+
member.each_pair do |action, methods|
|
367
|
+
behaviors << Behavior.new(
|
368
|
+
{ :path => %r{^/:id[/;]#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
|
369
|
+
{ :action => action.to_s }, next_level
|
370
|
+
)
|
371
|
+
next_level.match("/:id/#{action}").to_route.name(:"#{action}_#{route_singular_name}")
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
if collection = options.delete(:collection)
|
376
|
+
collection.each_pair do |action, methods|
|
377
|
+
behaviors << Behavior.new(
|
378
|
+
{ :path => %r{^[/;]#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
|
379
|
+
{ :action => action.to_s }, next_level
|
380
|
+
)
|
381
|
+
next_level.match("/#{action}").to_route.name(:"#{action}_#{route_plural_name}")
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
340
385
|
routes = many_behaviors_to(behaviors + next_level.send(:resources_behaviors), options)
|
341
|
-
|
386
|
+
|
342
387
|
# Add names to some routes
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
388
|
+
[['', :"#{route_plural_name}"],
|
389
|
+
['/:id', :"#{route_singular_name}"],
|
390
|
+
['/new', :"new_#{route_singular_name}"],
|
391
|
+
['/:id/edit', :"edit_#{route_singular_name}"],
|
392
|
+
['/:action/:id', :"custom_#{route_singular_name}"]
|
393
|
+
].each do |path,name|
|
394
|
+
next_level.match(path).to_route.name(name)
|
395
|
+
end
|
396
|
+
|
349
397
|
yield next_level.match("/:#{singular}_id") if block_given?
|
398
|
+
|
350
399
|
routes
|
351
400
|
end
|
352
|
-
|
353
|
-
def to_resource(params = {}, &block)
|
354
|
-
many_behaviors_to(resource_behaviors, params, &block)
|
355
|
-
end
|
356
|
-
|
401
|
+
|
357
402
|
def resource(name, options = {})
|
358
|
-
|
359
|
-
|
403
|
+
namespace = options[:namespace] || merged_params[:namespace] || conditions[:namespace]
|
404
|
+
match_path = namespace ? "/#{namespace}/#{name}" : "/#{name}"
|
405
|
+
next_level = match match_path
|
406
|
+
|
360
407
|
options[:controller] ||= merged_params[:controller] || name.to_s
|
361
|
-
name_prefix = options.delete(:name_prefix)
|
362
|
-
routes = next_level.to_resource(options)
|
363
|
-
|
364
|
-
namespace = options[:namespace] || merged_params[:namespace]
|
365
408
|
|
366
|
-
|
367
|
-
|
409
|
+
# Do not pass :name_prefix option on to to_resource
|
410
|
+
name_prefix = options.delete :name_prefix
|
411
|
+
|
412
|
+
if name_prefix.nil? && !namespace.nil?
|
413
|
+
name_prefix = namespace_to_name_prefix namespace
|
368
414
|
end
|
369
415
|
|
416
|
+
routes = next_level.to_resource options
|
417
|
+
|
370
418
|
route_name = "#{name_prefix}#{name}"
|
371
|
-
|
372
|
-
next_level.match(
|
373
|
-
next_level.match(
|
374
|
-
next_level.match(
|
375
|
-
|
419
|
+
|
420
|
+
next_level.match('').to_route.name(:"#{route_name}")
|
421
|
+
next_level.match('/new').to_route.name(:"new_#{route_name}")
|
422
|
+
next_level.match('/edit').to_route.name(:"edit_#{route_name}")
|
423
|
+
|
376
424
|
yield next_level if block_given?
|
425
|
+
|
377
426
|
routes
|
378
427
|
end
|
379
|
-
|
428
|
+
|
429
|
+
def to_resources(params = {}, &block)
|
430
|
+
many_behaviors_to resources_behaviors, params, &block
|
431
|
+
end
|
432
|
+
|
433
|
+
def to_resource(params = {}, &block)
|
434
|
+
many_behaviors_to resource_behaviors, params, &block
|
435
|
+
end
|
436
|
+
|
380
437
|
def merged_original_conditions
|
381
438
|
if parent.nil?
|
382
439
|
@original_conditions
|
383
440
|
else
|
384
441
|
merged_so_far = parent.merged_original_conditions
|
385
|
-
path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path])
|
386
|
-
|
387
|
-
|
442
|
+
if path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path])
|
443
|
+
merged_so_far.merge(@original_conditions).merge(:path => path)
|
444
|
+
else
|
388
445
|
merged_so_far.merge(@original_conditions)
|
446
|
+
end
|
389
447
|
end
|
390
448
|
end
|
391
|
-
|
449
|
+
|
392
450
|
def merged_conditions
|
393
451
|
if parent.nil?
|
394
452
|
@conditions
|
395
453
|
else
|
396
454
|
merged_so_far = parent.merged_conditions
|
397
|
-
path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path])
|
398
|
-
|
399
|
-
|
455
|
+
if path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path])
|
456
|
+
merged_so_far.merge(@conditions).merge(:path => path)
|
457
|
+
else
|
400
458
|
merged_so_far.merge(@conditions)
|
459
|
+
end
|
401
460
|
end
|
402
461
|
end
|
403
|
-
|
462
|
+
|
404
463
|
def merged_params
|
405
464
|
if parent.nil?
|
406
465
|
@params
|
@@ -408,75 +467,82 @@ module Merb
|
|
408
467
|
parent.merged_params.merge(@params)
|
409
468
|
end
|
410
469
|
end
|
411
|
-
|
470
|
+
|
412
471
|
def merged_placeholders
|
413
472
|
placeholders = {}
|
414
473
|
(ancestors.reverse + [self]).each do |a|
|
415
|
-
a.placeholders.each_pair do |
|
474
|
+
a.placeholders.each_pair do |k, pair|
|
416
475
|
param, place = pair
|
417
|
-
placeholders[
|
476
|
+
placeholders[k] = [param, place + (param == :path ? a.total_previous_captures : 0)]
|
418
477
|
end
|
419
478
|
end
|
420
479
|
placeholders
|
421
480
|
end
|
422
|
-
|
481
|
+
|
423
482
|
def inspect
|
424
|
-
"[captures: #{path_captures}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]"
|
483
|
+
"[captures: #{path_captures.inspect}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]"
|
425
484
|
end
|
426
|
-
|
485
|
+
|
427
486
|
def regexp?
|
428
487
|
@conditions_have_regexp
|
429
488
|
end
|
430
|
-
|
489
|
+
|
431
490
|
protected
|
491
|
+
def namespace_to_name_prefix(name_or_path)
|
492
|
+
name_or_path.to_s.tr('/', '_') + '_'
|
493
|
+
end
|
494
|
+
|
432
495
|
def resources_behaviors(parent = self)
|
433
496
|
[
|
434
|
-
Behavior.new({:path => %r[^/?(\.:format)?$], :method => :get}, {:action => "index"}, parent),
|
435
|
-
Behavior.new({:path => %r[^/index(\.:format)?$], :method => :get}, {:action => "index"}, parent),
|
436
|
-
Behavior.new({:path => %r[^/new$], :method => :get}, {:action => "new"}, parent),
|
437
|
-
Behavior.new({:path => %r[^/?(\.:format)?$], :method => :post}, {:action => "create"}, parent),
|
438
|
-
Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :get}, {:action => "show"}, parent),
|
439
|
-
Behavior.new({:path => %r[^/:id[;/]edit$], :method => :get}, {:action => "edit"}, parent),
|
440
|
-
Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :put}, {:action => "update"}, parent),
|
441
|
-
Behavior.new({:path => %r[^/:id(\.:format)?$], :method => :delete}, {:action => "destroy"}, parent)
|
497
|
+
Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :get }, { :action => "index" }, parent),
|
498
|
+
Behavior.new({ :path => %r[^/index(\.:format)?$], :method => :get }, { :action => "index" }, parent),
|
499
|
+
Behavior.new({ :path => %r[^/new$], :method => :get }, { :action => "new" }, parent),
|
500
|
+
Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :post }, { :action => "create" }, parent),
|
501
|
+
Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :get }, { :action => "show" }, parent),
|
502
|
+
Behavior.new({ :path => %r[^/:id[;/]edit$], :method => :get }, { :action => "edit" }, parent),
|
503
|
+
Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :put }, { :action => "update" }, parent),
|
504
|
+
Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :delete }, { :action => "destroy" }, parent)
|
442
505
|
]
|
443
506
|
end
|
444
|
-
|
507
|
+
|
445
508
|
def resource_behaviors(parent = self)
|
446
509
|
[
|
447
|
-
Behavior.new({:path => %r{^[;/]new$}, :method => :get}, {:action => "new"},
|
448
|
-
Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :post}, {:action => "create"},
|
449
|
-
Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :get}, {:action => "show"},
|
450
|
-
Behavior.new({:path => %r{^[;/]edit$}, :method => :get}, {:action => "edit"},
|
451
|
-
Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :put}, {:action => "update"},
|
452
|
-
Behavior.new({:path => %r{^/?(\.:format)?$}, :method => :delete}, {:action => "destroy"},
|
510
|
+
Behavior.new({ :path => %r{^[;/]new$}, :method => :get }, { :action => "new" }, parent),
|
511
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :post }, { :action => "create" }, parent),
|
512
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :get }, { :action => "show" }, parent),
|
513
|
+
Behavior.new({ :path => %r{^[;/]edit$}, :method => :get }, { :action => "edit" }, parent),
|
514
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :put }, { :action => "update" }, parent),
|
515
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :delete }, { :action => "destroy" }, parent)
|
453
516
|
]
|
454
517
|
end
|
455
|
-
|
518
|
+
|
456
519
|
# Creates a series of routes from an array of Behavior objects.
|
457
520
|
# You can pass in optional +params+, and an optional block that will be
|
458
521
|
# passed along to the #to method.
|
459
522
|
def many_behaviors_to(behaviors, params = {}, &conditional_block)
|
460
|
-
|
461
|
-
behaviors.each { |b| routes << b.to(params, &conditional_block) }
|
462
|
-
routes
|
523
|
+
behaviors.map { |b| b.to params, &conditional_block }
|
463
524
|
end
|
464
|
-
|
525
|
+
|
465
526
|
# Convert conditions to regular expression string sources for consistency
|
466
527
|
def stringify_conditions
|
467
528
|
@conditions_have_regexp = false
|
468
|
-
@conditions.each_pair do |
|
529
|
+
@conditions.each_pair do |k,v|
|
469
530
|
# TODO: Other Regexp special chars
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
531
|
+
|
532
|
+
@conditions[k] = case v
|
533
|
+
when String,Symbol
|
534
|
+
if k == :namespace
|
535
|
+
v.to_s
|
536
|
+
else
|
537
|
+
"^#{v.to_s.escape_regexp}$"
|
476
538
|
end
|
539
|
+
when Regexp
|
540
|
+
@conditions_have_regexp = true
|
541
|
+
v.source
|
542
|
+
end
|
477
543
|
end
|
478
544
|
end
|
479
|
-
|
545
|
+
|
480
546
|
def copy_original_conditions
|
481
547
|
@original_conditions = {}
|
482
548
|
@conditions.each_pair do |key, value|
|
@@ -484,60 +550,66 @@ module Merb
|
|
484
550
|
end
|
485
551
|
@original_conditions
|
486
552
|
end
|
487
|
-
|
553
|
+
|
488
554
|
def deduce_placeholders
|
489
555
|
@conditions.each_pair do |match_key, source|
|
490
556
|
while match = SEGMENT_REGEXP.match(source)
|
491
|
-
source.sub!
|
492
|
-
unless match[2] ==
|
557
|
+
source.sub! SEGMENT_REGEXP, PARENTHETICAL_SEGMENT_STRING
|
558
|
+
unless match[2] == ':' # No need to store anonymous place holders
|
493
559
|
placeholder_key = match[2].intern
|
494
560
|
@params[placeholder_key] = "#{match[1]}"
|
495
|
-
@placeholders[placeholder_key] = [
|
561
|
+
@placeholders[placeholder_key] = [
|
562
|
+
match_key, Behavior.count_parens_up_to(source, match.offset(1)[0])
|
563
|
+
]
|
496
564
|
end
|
497
565
|
end
|
498
566
|
end
|
499
567
|
end
|
500
|
-
|
568
|
+
|
501
569
|
def ancestors(list = [])
|
502
570
|
if parent.nil?
|
503
571
|
list
|
504
572
|
else
|
505
573
|
list.push parent
|
506
|
-
parent.ancestors
|
574
|
+
parent.ancestors list
|
507
575
|
list
|
508
576
|
end
|
509
577
|
end
|
510
|
-
|
578
|
+
|
511
579
|
# Count the number of regexp captures in the :path condition
|
512
580
|
def path_captures
|
513
581
|
return 0 unless conditions[:path]
|
514
582
|
Behavior.count_parens_up_to(conditions[:path], conditions[:path].size)
|
515
583
|
end
|
516
|
-
|
584
|
+
|
517
585
|
def total_previous_captures
|
518
586
|
ancestors.map{|a| a.path_captures}.inject(0){|sum, n| sum + n}
|
519
587
|
end
|
520
|
-
|
588
|
+
|
521
589
|
# def merge_with_ancestors
|
522
590
|
# self.class.new(merged_conditions, merged_params)
|
523
591
|
# end
|
524
|
-
|
592
|
+
|
525
593
|
def compiled_conditions(conditions = merged_conditions)
|
526
|
-
|
527
|
-
|
528
|
-
|
594
|
+
conditions.inject({}) do |compiled,(k,v)|
|
595
|
+
compiled.merge k => Regexp.new(v)
|
596
|
+
end
|
529
597
|
end
|
530
|
-
|
598
|
+
|
531
599
|
# Compiles the params hash into 'eval'-able form.
|
532
|
-
#
|
600
|
+
#
|
533
601
|
# @params = {:controller => "admin/:controller"}
|
602
|
+
#
|
534
603
|
# Could become:
|
535
|
-
#
|
604
|
+
#
|
605
|
+
# { :controller => "'admin/' + matches[:path][1]" }
|
536
606
|
#
|
537
607
|
def compiled_params(params = merged_params, placeholders = merged_placeholders)
|
538
608
|
compiled = {}
|
539
609
|
params.each_pair do |key, value|
|
540
|
-
|
610
|
+
unless value.is_a? String
|
611
|
+
raise ArgumentError, "param value must be string (#{value.inspect})"
|
612
|
+
end
|
541
613
|
result = []
|
542
614
|
value = value.dup
|
543
615
|
match = true
|
@@ -568,41 +640,7 @@ module Merb
|
|
568
640
|
end
|
569
641
|
compiled
|
570
642
|
end
|
571
|
-
|
572
|
-
public
|
573
|
-
# Count the number of open parentheses in +string+, up to and including +pos+
|
574
|
-
def self.count_parens_up_to(string, pos)
|
575
|
-
string[0..pos].gsub(/[^\(]/, "").size
|
576
|
-
end
|
577
|
-
|
578
|
-
# Concatenate strings and remove regexp end caps
|
579
|
-
def self.concat_without_endcaps(string1, string2)
|
580
|
-
return nil if !string1 and !string2
|
581
|
-
return string1 if string2.nil?
|
582
|
-
return string2 if string1.nil?
|
583
|
-
s1 = string1[-1] == ?$ ? string1[0..-2] : string1
|
584
|
-
s2 = string2[0] == ?^ ? string2[1..-1] : string2
|
585
|
-
s1 + s2
|
586
|
-
end
|
587
|
-
|
588
|
-
# Join an array's elements into a string using " + " as a joiner, and
|
589
|
-
# surround string elements in quotes.
|
590
|
-
def self.array_to_code(arr)
|
591
|
-
code = ""
|
592
|
-
arr.each_with_index do |part, i|
|
593
|
-
code << " + " if i > 0
|
594
|
-
case part
|
595
|
-
when Symbol
|
596
|
-
code << "#{part}"
|
597
|
-
when String
|
598
|
-
code << "\"" + part + "\""
|
599
|
-
else
|
600
|
-
raise "Don't know how to compile array part: #{part.class} [#{i}]"
|
601
|
-
end
|
602
|
-
end
|
603
|
-
code
|
604
|
-
end
|
605
643
|
end # Behavior
|
606
|
-
|
644
|
+
|
607
645
|
end
|
608
646
|
end
|