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.
- 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
|