thorero-core 0.9.4.6
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README +21 -0
- data/Rakefile +352 -0
- data/TODO +0 -0
- data/bin/merb +12 -0
- data/bin/merb-specs +5 -0
- data/docs/bootloading.dox +58 -0
- data/docs/documentation_standards +40 -0
- data/docs/merb-core-call-stack-diagram.mmap +0 -0
- data/docs/merb-core-call-stack-diagram.pdf +0 -0
- data/docs/merb-core-call-stack-diagram.png +0 -0
- data/docs/new_render_api +51 -0
- data/lib/merb-core.rb +603 -0
- data/lib/merb-core/autoload.rb +32 -0
- data/lib/merb-core/bootloader.rb +708 -0
- data/lib/merb-core/config.rb +303 -0
- data/lib/merb-core/constants.rb +43 -0
- data/lib/merb-core/controller/abstract_controller.rb +578 -0
- data/lib/merb-core/controller/exceptions.rb +302 -0
- data/lib/merb-core/controller/merb_controller.rb +256 -0
- data/lib/merb-core/controller/mime.rb +106 -0
- data/lib/merb-core/controller/mixins/authentication.rb +87 -0
- data/lib/merb-core/controller/mixins/controller.rb +290 -0
- data/lib/merb-core/controller/mixins/render.rb +481 -0
- data/lib/merb-core/controller/mixins/responder.rb +472 -0
- data/lib/merb-core/controller/template.rb +254 -0
- data/lib/merb-core/core_ext.rb +8 -0
- data/lib/merb-core/core_ext/kernel.rb +319 -0
- data/lib/merb-core/dispatch/cookies.rb +91 -0
- data/lib/merb-core/dispatch/dispatcher.rb +278 -0
- data/lib/merb-core/dispatch/exceptions.html.erb +303 -0
- data/lib/merb-core/dispatch/request.rb +603 -0
- data/lib/merb-core/dispatch/router.rb +179 -0
- data/lib/merb-core/dispatch/router/behavior.rb +867 -0
- data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
- data/lib/merb-core/dispatch/router/route.rb +321 -0
- data/lib/merb-core/dispatch/session.rb +78 -0
- data/lib/merb-core/dispatch/session/cookie.rb +168 -0
- data/lib/merb-core/dispatch/session/memcached.rb +184 -0
- data/lib/merb-core/dispatch/session/memory.rb +241 -0
- data/lib/merb-core/dispatch/worker.rb +28 -0
- data/lib/merb-core/gem_ext/erubis.rb +77 -0
- data/lib/merb-core/logger.rb +202 -0
- data/lib/merb-core/plugins.rb +59 -0
- data/lib/merb-core/rack.rb +21 -0
- data/lib/merb-core/rack/adapter.rb +44 -0
- data/lib/merb-core/rack/adapter/ebb.rb +25 -0
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
- data/lib/merb-core/rack/adapter/irb.rb +118 -0
- data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/runner.rb +28 -0
- data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/thin.rb +39 -0
- data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
- data/lib/merb-core/rack/adapter/webrick.rb +36 -0
- data/lib/merb-core/rack/application.rb +18 -0
- data/lib/merb-core/rack/handler/mongrel.rb +97 -0
- data/lib/merb-core/rack/middleware.rb +26 -0
- data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
- data/lib/merb-core/rack/middleware/profiler.rb +19 -0
- data/lib/merb-core/rack/middleware/static.rb +45 -0
- data/lib/merb-core/server.rb +252 -0
- data/lib/merb-core/tasks/audit.rake +68 -0
- data/lib/merb-core/tasks/merb.rb +1 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +12 -0
- data/lib/merb-core/test.rb +11 -0
- data/lib/merb-core/test/helpers.rb +9 -0
- data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
- data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
- data/lib/merb-core/test/helpers/request_helper.rb +344 -0
- data/lib/merb-core/test/helpers/route_helper.rb +33 -0
- data/lib/merb-core/test/helpers/view_helper.rb +121 -0
- data/lib/merb-core/test/matchers.rb +9 -0
- data/lib/merb-core/test/matchers/controller_matchers.rb +319 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +136 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +335 -0
- data/lib/merb-core/test/run_specs.rb +47 -0
- data/lib/merb-core/test/tasks/spectasks.rb +68 -0
- data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
- data/lib/merb-core/test/test_ext/object.rb +14 -0
- data/lib/merb-core/test/test_ext/string.rb +14 -0
- data/lib/merb-core/vendor/facets.rb +2 -0
- data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
- data/lib/merb-core/vendor/facets/inflect.rb +345 -0
- data/lib/merb-core/version.rb +11 -0
- data/spec/private/config/adapter_spec.rb +32 -0
- data/spec/private/config/config_spec.rb +202 -0
- data/spec/private/config/environment_spec.rb +13 -0
- data/spec/private/config/spec_helper.rb +1 -0
- data/spec/private/core_ext/kernel_spec.rb +169 -0
- data/spec/private/dispatch/bootloader_spec.rb +24 -0
- data/spec/private/dispatch/cookies_spec.rb +107 -0
- data/spec/private/dispatch/dispatch_spec.rb +35 -0
- data/spec/private/dispatch/fixture/app/controllers/application.rb +4 -0
- data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +27 -0
- data/spec/private/dispatch/fixture/app/controllers/foo.rb +21 -0
- data/spec/private/dispatch/fixture/app/helpers/global_helpers.rb +8 -0
- data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +37 -0
- data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +216 -0
- data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +38 -0
- data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +40 -0
- data/spec/private/dispatch/fixture/app/views/foo/bar.html.erb +0 -0
- data/spec/private/dispatch/fixture/app/views/layout/application.html.erb +11 -0
- data/spec/private/dispatch/fixture/config/black_hole.rb +12 -0
- data/spec/private/dispatch/fixture/config/environments/development.rb +6 -0
- data/spec/private/dispatch/fixture/config/environments/production.rb +5 -0
- data/spec/private/dispatch/fixture/config/environments/test.rb +6 -0
- data/spec/private/dispatch/fixture/config/init.rb +45 -0
- data/spec/private/dispatch/fixture/config/rack.rb +11 -0
- data/spec/private/dispatch/fixture/config/router.rb +35 -0
- data/spec/private/dispatch/fixture/log/merb_test.log +1874 -0
- data/spec/private/dispatch/fixture/public/images/merb.jpg +0 -0
- data/spec/private/dispatch/fixture/public/merb.fcgi +4 -0
- data/spec/private/dispatch/fixture/public/stylesheets/master.css +119 -0
- data/spec/private/dispatch/route_params_spec.rb +24 -0
- data/spec/private/dispatch/session_mixin_spec.rb +47 -0
- data/spec/private/dispatch/spec_helper.rb +1 -0
- data/spec/private/plugins/plugin_spec.rb +166 -0
- data/spec/private/rack/application_spec.rb +49 -0
- data/spec/private/router/behavior_spec.rb +60 -0
- data/spec/private/router/fixture/log/merb_test.log +139 -0
- data/spec/private/router/route_spec.rb +414 -0
- data/spec/private/router/router_spec.rb +175 -0
- data/spec/private/vendor/facets/plural_spec.rb +564 -0
- data/spec/private/vendor/facets/singular_spec.rb +489 -0
- data/spec/public/DEFINITIONS +11 -0
- data/spec/public/abstract_controller/controllers/alt_views/layout/application.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_string_controller_layout.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_template_controller_layout.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/show.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/partial/basic_partial_with_multiple_roots/_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_and_custom_location/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_inherited/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/cousins.rb +41 -0
- data/spec/public/abstract_controller/controllers/display.rb +54 -0
- data/spec/public/abstract_controller/controllers/filters.rb +193 -0
- data/spec/public/abstract_controller/controllers/helpers.rb +41 -0
- data/spec/public/abstract_controller/controllers/partial.rb +121 -0
- data/spec/public/abstract_controller/controllers/render.rb +113 -0
- data/spec/public/abstract_controller/controllers/views/helpers/capture/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/helpers/capture_eq/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/helpers/capture_with_args/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/helpers/concat/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/layout/alt.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/layout/custom.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object_with_action/new.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_app_layout/index.erb +0 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_custom_layout/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/show.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_two_throw_contents/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/another_directory/_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial/_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial_with_multiple_roots/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_first.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_second.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_in_another_directory/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/_collection.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/_collection.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/_collection.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/_collection.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/_variables.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/_both.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/_with_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/_with_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_partial/_with_partial.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/partial/with_partial/index.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/test_display/foo.html.erb +1 -0
- data/spec/public/abstract_controller/controllers/views/test_render/foo.html.erb +0 -0
- data/spec/public/abstract_controller/controllers/views/wonderful/index.erb +1 -0
- data/spec/public/abstract_controller/display_spec.rb +33 -0
- data/spec/public/abstract_controller/filter_spec.rb +106 -0
- data/spec/public/abstract_controller/helper_spec.rb +21 -0
- data/spec/public/abstract_controller/partial_spec.rb +61 -0
- data/spec/public/abstract_controller/render_spec.rb +90 -0
- data/spec/public/abstract_controller/spec_helper.rb +31 -0
- data/spec/public/boot_loader/boot_loader_spec.rb +33 -0
- data/spec/public/boot_loader/spec_helper.rb +1 -0
- data/spec/public/controller/authentication_spec.rb +103 -0
- data/spec/public/controller/base_spec.rb +36 -0
- data/spec/public/controller/controllers/authentication.rb +45 -0
- data/spec/public/controller/controllers/base.rb +36 -0
- data/spec/public/controller/controllers/display.rb +118 -0
- data/spec/public/controller/controllers/redirect.rb +30 -0
- data/spec/public/controller/controllers/responder.rb +93 -0
- data/spec/public/controller/controllers/url.rb +7 -0
- data/spec/public/controller/controllers/views/layout/custom.html.erb +1 -0
- data/spec/public/controller/controllers/views/layout/custom_arg.html.erb +1 -0
- data/spec/public/controller/controllers/views/layout/custom_arg.json.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.xml.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.xml.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/no_layout.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template_argument/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/html_default/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/layout/custom.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.xml.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.html.erb +1 -0
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.js.erb +1 -0
- data/spec/public/controller/display_spec.rb +84 -0
- data/spec/public/controller/redirect_spec.rb +27 -0
- data/spec/public/controller/responder_spec.rb +163 -0
- data/spec/public/controller/spec_helper.rb +11 -0
- data/spec/public/controller/url_spec.rb +180 -0
- data/spec/public/core/merb_core_spec.rb +45 -0
- data/spec/public/core_ext/class_spec.rb +91 -0
- data/spec/public/core_ext/fixtures/core_ext_dependency.rb +2 -0
- data/spec/public/core_ext/kernel_spec.rb +9 -0
- data/spec/public/core_ext/spec_helper.rb +1 -0
- data/spec/public/directory_structure/directory/app/controllers/application.rb +3 -0
- data/spec/public/directory_structure/directory/app/controllers/base.rb +13 -0
- data/spec/public/directory_structure/directory/app/controllers/custom.rb +19 -0
- data/spec/public/directory_structure/directory/app/views/base/template.html.erb +1 -0
- data/spec/public/directory_structure/directory/app/views/wonderful/template.erb +1 -0
- data/spec/public/directory_structure/directory/config/router.rb +3 -0
- data/spec/public/directory_structure/directory/log/merb_test.log +562 -0
- data/spec/public/directory_structure/directory_spec.rb +44 -0
- data/spec/public/logger/logger_spec.rb +181 -0
- data/spec/public/logger/spec_helper.rb +1 -0
- data/spec/public/reloading/directory/app/controllers/application.rb +3 -0
- data/spec/public/reloading/directory/app/controllers/reload.rb +6 -0
- data/spec/public/reloading/directory/config/init.rb +2 -0
- data/spec/public/reloading/directory/log/merb_test.log +138 -0
- data/spec/public/reloading/reload_spec.rb +103 -0
- data/spec/public/request/multipart_spec.rb +41 -0
- data/spec/public/request/request_spec.rb +228 -0
- data/spec/public/router/default_spec.rb +21 -0
- data/spec/public/router/deferred_spec.rb +22 -0
- data/spec/public/router/fixation_spec.rb +27 -0
- data/spec/public/router/fixture/log/merb_test.log +1556 -0
- data/spec/public/router/namespace_spec.rb +113 -0
- data/spec/public/router/nested_matches_spec.rb +97 -0
- data/spec/public/router/nested_resources_spec.rb +41 -0
- data/spec/public/router/resource_spec.rb +37 -0
- data/spec/public/router/resources_spec.rb +82 -0
- data/spec/public/router/spec_helper.rb +90 -0
- data/spec/public/router/special_spec.rb +61 -0
- data/spec/public/router/string_spec.rb +61 -0
- data/spec/public/template/template_spec.rb +104 -0
- data/spec/public/template/templates/error.html.erb +2 -0
- data/spec/public/template/templates/template.html.erb +1 -0
- data/spec/public/template/templates/template.html.myt +1 -0
- data/spec/public/test/controller_matchers_spec.rb +402 -0
- data/spec/public/test/controllers/controller_assertion_mock.rb +7 -0
- data/spec/public/test/controllers/dispatch_controller.rb +11 -0
- data/spec/public/test/controllers/spec_helper_controller.rb +38 -0
- data/spec/public/test/multipart_request_helper_spec.rb +159 -0
- data/spec/public/test/multipart_upload_text_file.txt +1 -0
- data/spec/public/test/request_helper_spec.rb +221 -0
- data/spec/public/test/route_helper_spec.rb +71 -0
- data/spec/public/test/route_matchers_spec.rb +162 -0
- data/spec/public/test/view_helper_spec.rb +96 -0
- data/spec/public/test/view_matchers_spec.rb +183 -0
- data/spec/spec_helper.rb +68 -0
- metadata +542 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'merb-core/dispatch/router/cached_proc'
|
2
|
+
require 'merb-core/dispatch/router/behavior'
|
3
|
+
require 'merb-core/dispatch/router/route'
|
4
|
+
require 'merb-core/controller/mixins/responder'
|
5
|
+
module Merb
|
6
|
+
# Router stores route definitions and finds first
|
7
|
+
# that matches incoming request URL.
|
8
|
+
#
|
9
|
+
# Then information from route is used by dispatcher to
|
10
|
+
# call action on the controller.
|
11
|
+
#
|
12
|
+
# ==== Routes compilation.
|
13
|
+
#
|
14
|
+
# Most interesting method of Router (and heart of
|
15
|
+
# route matching machinery) is match method generated
|
16
|
+
# on fly from routes definitions. It is called routes
|
17
|
+
# compilation. Generated match method body contains
|
18
|
+
# one if/elsif statement that picks first matching route
|
19
|
+
# definition and sets values to named parameters of the route.
|
20
|
+
#
|
21
|
+
# Compilation is synchronized by mutex.
|
22
|
+
class Router
|
23
|
+
SEGMENT_REGEXP = /(:([a-z_][a-z0-9_]*|:))/
|
24
|
+
SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
|
25
|
+
JUST_BRACKETS = /\[(\d+)\]/
|
26
|
+
PARENTHETICAL_SEGMENT_STRING = "([^\/.,;?]+)".freeze
|
27
|
+
|
28
|
+
@@named_routes = {}
|
29
|
+
@@routes = []
|
30
|
+
@@compiler_mutex = Mutex.new
|
31
|
+
cattr_accessor :routes, :named_routes
|
32
|
+
|
33
|
+
class << self
|
34
|
+
|
35
|
+
# Clear all routes.
|
36
|
+
def reset!
|
37
|
+
self.routes, self.named_routes = [], {}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Appends the generated routes to the current routes.
|
41
|
+
#
|
42
|
+
# ==== Parameters
|
43
|
+
# &block::
|
44
|
+
# A block that generates new routes when yielded a new Behavior.
|
45
|
+
def append(&block)
|
46
|
+
prepare(@@routes, [], &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Prepends the generated routes to the current routes.
|
50
|
+
#
|
51
|
+
# ==== Parameters
|
52
|
+
# &block::
|
53
|
+
# A block that generates new routes when yielded a new Behavior.
|
54
|
+
def prepend(&block)
|
55
|
+
prepare([], @@routes, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Prepares new routes and adds them to existing routes.
|
59
|
+
#
|
60
|
+
# ==== Parameters
|
61
|
+
# first<Array>:: An array of routes to add before the generated routes.
|
62
|
+
# last<Array>:: An array of routes to add after the generated routes.
|
63
|
+
# &block:: A block that generates new routes.
|
64
|
+
#
|
65
|
+
# ==== Block parameters (&block)
|
66
|
+
# new_behavior<Behavior>:: Behavior for child routes.
|
67
|
+
def prepare(first = [], last = [], &block)
|
68
|
+
@@routes = []
|
69
|
+
yield Behavior.new({}, { :action => 'index' }) # defaults
|
70
|
+
@@routes = first + @@routes + last
|
71
|
+
compile
|
72
|
+
end
|
73
|
+
|
74
|
+
# Capture any new routes that have been added within the block.
|
75
|
+
#
|
76
|
+
# This utility method lets you track routes that have been added;
|
77
|
+
# it doesn't affect how/which routes are added.
|
78
|
+
#
|
79
|
+
# &block:: A context in which routes are generated.
|
80
|
+
def capture(&block)
|
81
|
+
routes_before, named_route_keys_before = self.routes.dup, self.named_routes.keys
|
82
|
+
yield
|
83
|
+
[self.routes - routes_before, self.named_routes.except(*named_route_keys_before)]
|
84
|
+
end
|
85
|
+
|
86
|
+
# ==== Returns
|
87
|
+
# String:: A routing lambda statement generated from the routes.
|
88
|
+
def compiled_statement
|
89
|
+
@@compiler_mutex.synchronize do
|
90
|
+
@@compiled_statement = "def match(request)\n"
|
91
|
+
@@compiled_statement << " params = request.params\n"
|
92
|
+
@@compiled_statement << " cached_path = request.path\n cached_method = request.method.to_s\n "
|
93
|
+
@@routes.each_with_index { |route, i| @@compiled_statement << route.compile(i == 0) }
|
94
|
+
@@compiled_statement << " else\n [nil, {}]\n"
|
95
|
+
@@compiled_statement << " end\n"
|
96
|
+
@@compiled_statement << "end"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Defines the match function for this class based on the
|
101
|
+
# compiled_statement.
|
102
|
+
def compile
|
103
|
+
puts "compiled route: #{compiled_statement}" if $DEBUG
|
104
|
+
eval(compiled_statement, binding, "Generated Code for Router#match(#{__FILE__}:#{__LINE__})", 1)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Generates a URL based on passed options.
|
108
|
+
#
|
109
|
+
# ==== Parameters
|
110
|
+
# name<~to_sym, Hash>:: The name of the route to generate.
|
111
|
+
# params<Hash, Fixnum, Object>:: The params to use in the route generation.
|
112
|
+
# fallback<Hash>:: Parameters for generating a fallback URL.
|
113
|
+
#
|
114
|
+
# ==== Returns
|
115
|
+
# String:: The generated URL.
|
116
|
+
#
|
117
|
+
# ==== Alternatives
|
118
|
+
# If name is a hash, it will be merged with params and passed on to
|
119
|
+
# generate_for_default_route along with fallback.
|
120
|
+
def generate(name, params = {}, fallback = {})
|
121
|
+
params.reject! { |k,v| v.nil? } if params.is_a? Hash
|
122
|
+
if name.is_a? Hash
|
123
|
+
name.reject! { |k,v| v.nil? }
|
124
|
+
return generate_for_default_route(name.merge(params), fallback)
|
125
|
+
end
|
126
|
+
name = name.to_sym
|
127
|
+
unless @@named_routes.key? name
|
128
|
+
raise "Named route not found: #{name}"
|
129
|
+
else
|
130
|
+
@@named_routes[name].generate(params, fallback)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Generates a URL based on the default route scheme of
|
135
|
+
# "/:controller/:action/:id.:format".
|
136
|
+
#
|
137
|
+
# ==== Parameters
|
138
|
+
# params<Hash>::
|
139
|
+
# The primary parameters to create the route from (see below).
|
140
|
+
# fallback<Hash>:: Fallback parameters. Same options as params.
|
141
|
+
#
|
142
|
+
# ==== Options (params)
|
143
|
+
# :controller<~to_s>:: The controller name. Required.
|
144
|
+
# :action<~to_s>:: The action name. Required.
|
145
|
+
# :id<~to_s>:: The ID for use in the action.
|
146
|
+
# :format<~to_s>:: The format of the preferred response.
|
147
|
+
#
|
148
|
+
# ==== Returns
|
149
|
+
# String:: The generated URL.
|
150
|
+
def generate_for_default_route(params, fallback)
|
151
|
+
query_params = params.reject do |k,v|
|
152
|
+
[:controller, :action, :id, :format].include?(k.to_sym)
|
153
|
+
end
|
154
|
+
|
155
|
+
controller = params[:controller] || fallback[:controller]
|
156
|
+
raise "Controller Not Specified" unless controller
|
157
|
+
url = "/#{controller}"
|
158
|
+
|
159
|
+
if params[:action] || params[:id] || params[:format] || !query_params.empty?
|
160
|
+
action = params[:action] || fallback[:action]
|
161
|
+
raise "Action Not Specified" unless action
|
162
|
+
url += "/#{action}"
|
163
|
+
end
|
164
|
+
if params[:id]
|
165
|
+
url += "/#{params[:id]}"
|
166
|
+
end
|
167
|
+
if format = params[:format]
|
168
|
+
format = fallback[:format] if format == :current
|
169
|
+
url += ".#{format}"
|
170
|
+
end
|
171
|
+
unless query_params.empty?
|
172
|
+
url += "?" + Merb::Request.params_to_query_string(query_params)
|
173
|
+
end
|
174
|
+
url
|
175
|
+
end
|
176
|
+
end # self
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,867 @@
|
|
1
|
+
module Merb
|
2
|
+
|
3
|
+
class Router
|
4
|
+
|
5
|
+
# The Behavior class is an interim route-building class that ties
|
6
|
+
# pattern-matching +conditions+ to output parameters, +params+.
|
7
|
+
#---
|
8
|
+
# @public
|
9
|
+
class Behavior
|
10
|
+
attr_reader :placeholders, :conditions, :params, :redirect_url, :redirect_status
|
11
|
+
attr_accessor :parent
|
12
|
+
@@parent_resources = []
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# ==== Parameters
|
16
|
+
# string<String>:: The string in which to count parentheses.
|
17
|
+
# pos<Fixnum>:: The last character for counting.
|
18
|
+
#
|
19
|
+
# ==== Returns
|
20
|
+
# Fixnum::
|
21
|
+
# The number of open parentheses in string, up to and including pos.
|
22
|
+
def count_parens_up_to(string, pos)
|
23
|
+
string[0..pos].gsub(/[^\(]/, '').size
|
24
|
+
end
|
25
|
+
|
26
|
+
# ==== Parameters
|
27
|
+
# string1<String>:: The string to concatenate with.
|
28
|
+
# string2<String>:: The string to concatenate.
|
29
|
+
#
|
30
|
+
# ==== Returns
|
31
|
+
# String:: the concatenated string with regexp end caps removed.
|
32
|
+
def concat_without_endcaps(string1, string2)
|
33
|
+
return nil if !string1 and !string2
|
34
|
+
return string1 if string2.nil?
|
35
|
+
return string2 if string1.nil?
|
36
|
+
s1 = string1[-1] == ?$ ? string1[0..-2] : string1
|
37
|
+
s2 = string2[0] == ?^ ? string2[1..-1] : string2
|
38
|
+
s1 + s2
|
39
|
+
end
|
40
|
+
|
41
|
+
# ==== Parameters
|
42
|
+
# arr<Array>:: The array to convert to a code string.
|
43
|
+
#
|
44
|
+
# ==== Returns
|
45
|
+
# String::
|
46
|
+
# The arr's elements converted to string and joined with " + ", with
|
47
|
+
# any string elements surrounded by quotes.
|
48
|
+
def array_to_code(arr)
|
49
|
+
code = ''
|
50
|
+
arr.each_with_index do |part, i|
|
51
|
+
code << ' + ' if i > 0
|
52
|
+
case part
|
53
|
+
when Symbol
|
54
|
+
code << part.to_s
|
55
|
+
when String
|
56
|
+
code << %{"#{part}"}
|
57
|
+
else
|
58
|
+
raise "Don't know how to compile array part: #{part.class} [#{i}]"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
code
|
62
|
+
end
|
63
|
+
end # class << self
|
64
|
+
|
65
|
+
# ==== Parameters
|
66
|
+
# conditions<Hash>::
|
67
|
+
# Conditions to be met for this behavior to take effect.
|
68
|
+
# params<Hash>::
|
69
|
+
# Hash describing the course action to take (Behavior) when the
|
70
|
+
# conditions match. The values of the +params+ keys must be Strings.
|
71
|
+
# parent<Behavior, Nil>::
|
72
|
+
# The parent of this Behavior. Defaults to nil.
|
73
|
+
def initialize(conditions = {}, params = {}, parent = nil)
|
74
|
+
# Must wait until after deducing placeholders to set @params !
|
75
|
+
@conditions, @params, @parent = conditions, {}, parent
|
76
|
+
@placeholders = {}
|
77
|
+
stringify_conditions
|
78
|
+
copy_original_conditions
|
79
|
+
deduce_placeholders
|
80
|
+
@params.merge! params
|
81
|
+
end
|
82
|
+
|
83
|
+
# Register a new route.
|
84
|
+
#
|
85
|
+
# ==== Parameters
|
86
|
+
# path<String, Regex>:: The url path to match
|
87
|
+
# params<Hash>:: The parameters the new routes maps to.
|
88
|
+
#
|
89
|
+
# ==== Returns
|
90
|
+
# Route:: The resulting Route.
|
91
|
+
#---
|
92
|
+
# @public
|
93
|
+
def add(path, params = {})
|
94
|
+
match(path).to(params)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Matches a +path+ and any number of optional request methods as
|
98
|
+
# conditions of a route. Alternatively, +path+ can be a hash of
|
99
|
+
# conditions, in which case +conditions+ ignored.
|
100
|
+
#
|
101
|
+
# ==== Parameters
|
102
|
+
#
|
103
|
+
# path<String, Regexp>::
|
104
|
+
# When passing a string as +path+ you're defining a literal definition
|
105
|
+
# for your route. Using a colon, ex.: ":login", defines both a capture
|
106
|
+
# and a named param.
|
107
|
+
# When passing a regular expression you can define captures explicitly
|
108
|
+
# within the regular expression syntax.
|
109
|
+
# +path+ is optional.
|
110
|
+
# conditions<Hash>::
|
111
|
+
# Addational conditions that the request must meet in order to match.
|
112
|
+
# the keys must be methods that the Merb::Request instance will respond
|
113
|
+
# to. The value is the string or regexp that matched the returned value.
|
114
|
+
# Conditions are inherited by child routes.
|
115
|
+
#
|
116
|
+
# The Following have special meaning:
|
117
|
+
# * :method -- Limit this match based on the request method. (GET,
|
118
|
+
# POST, PUT, DELETE)
|
119
|
+
# * :path -- Used internally to maintain URL form information
|
120
|
+
# * :controller and :action -- These can be used here instead of '#to', and
|
121
|
+
# will be inherited in the block.
|
122
|
+
# * :params -- Sets other key/value pairs that are placed in the params
|
123
|
+
# hash. The value must be a hash.
|
124
|
+
# &block::
|
125
|
+
# Passes a new instance of a Behavior object into the optional block so
|
126
|
+
# that sub-matching and routes nesting may occur.
|
127
|
+
#
|
128
|
+
# ==== Returns
|
129
|
+
# Behavior::
|
130
|
+
# A new instance of Behavior with the specified path and conditions.
|
131
|
+
#
|
132
|
+
# +Tip+: When nesting always make sure the most inner sub-match registers
|
133
|
+
# a Route and doesn't just returns new Behaviors.
|
134
|
+
#
|
135
|
+
# ==== Examples
|
136
|
+
#
|
137
|
+
# # registers /foo/bar to controller => "foo", :action => "bar"
|
138
|
+
# # and /foo/baz to controller => "foo", :action => "baz"
|
139
|
+
# r.match "/foo", :controller=>"foo" do |f|
|
140
|
+
# f.match("/bar").to(:action => "bar")
|
141
|
+
# f.match("/baz").to(:action => "caz")
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# #match only of the browser string contains MSIE or Gecko
|
145
|
+
# r.match ('/foo', :user_agent => /(MSIE|Gecko)/ )
|
146
|
+
# .to({:controller=>'foo', :action=>'popular')
|
147
|
+
#
|
148
|
+
# # Route GET and POST requests to different actions (see also #resources)
|
149
|
+
# r.match('/foo', :method=>:get).to(:action=>'show')
|
150
|
+
# r.mathc('/foo', :method=>:post).to(:action=>'create')
|
151
|
+
#
|
152
|
+
# # match also takes regular expressions
|
153
|
+
#
|
154
|
+
# r.match(%r[/account/([a-z]{4,6})]).to(:controller => "account",
|
155
|
+
# :action => "show", :id => "[1]")
|
156
|
+
#
|
157
|
+
# r.match(/\/?(en|es|fr|be|nl)?/).to(:language => "[1]") do |l|
|
158
|
+
# l.match("/guides/:action/:id").to(:controller => "tour_guides")
|
159
|
+
# end
|
160
|
+
#---
|
161
|
+
# @public
|
162
|
+
def match(path = '', conditions = {}, &block)
|
163
|
+
if path.is_a? Hash
|
164
|
+
conditions = path
|
165
|
+
else
|
166
|
+
conditions[:path] = path
|
167
|
+
end
|
168
|
+
match_without_path(conditions, &block)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Generates a new child behavior without the path if the path matches
|
172
|
+
# an empty string. Yields the new behavior to a block.
|
173
|
+
#
|
174
|
+
# ==== Parameters
|
175
|
+
# conditions<Hash>:: Optional conditions to pass to the new route.
|
176
|
+
#
|
177
|
+
# ==== Block parameters
|
178
|
+
# new_behavior<Behavior>:: The child behavior.
|
179
|
+
#
|
180
|
+
# ==== Returns
|
181
|
+
# Behavior:: The new behavior.
|
182
|
+
def match_without_path(conditions = {})
|
183
|
+
params = conditions.delete(:params) || {} #parents params will be merged in Route#new
|
184
|
+
params[:controller] = conditions.delete(:controller) if conditions[:controller]
|
185
|
+
params[:action] = conditions.delete(:action) if conditions[:action]
|
186
|
+
new_behavior = self.class.new(conditions, params, self)
|
187
|
+
yield new_behavior if block_given?
|
188
|
+
new_behavior
|
189
|
+
end
|
190
|
+
|
191
|
+
# ==== Parameters
|
192
|
+
# params<Hash>:: Optional additional parameters for generating the route.
|
193
|
+
# &conditional_block:: A conditional block to be passed to Route.new.
|
194
|
+
#
|
195
|
+
# ==== Returns
|
196
|
+
# Route:: A new route based on this behavior.
|
197
|
+
def to_route(params = {}, &conditional_block)
|
198
|
+
@params.merge! params
|
199
|
+
Route.new compiled_conditions, compiled_params, self, &conditional_block
|
200
|
+
end
|
201
|
+
|
202
|
+
# Combines common case of match being used with
|
203
|
+
# to({}).
|
204
|
+
#
|
205
|
+
# ==== Returns
|
206
|
+
# <Route>:: route that uses params from named path segments.
|
207
|
+
#
|
208
|
+
# ==== Examples
|
209
|
+
# r.match!("/api/:token/:controller/:action/:id")
|
210
|
+
#
|
211
|
+
# is the same thing as
|
212
|
+
#
|
213
|
+
# r.match!("/api/:token/:controller/:action/:id").to({})
|
214
|
+
def match!(path = '', conditions = {}, &block)
|
215
|
+
self.match(path, conditions, &block).to({})
|
216
|
+
end
|
217
|
+
|
218
|
+
# Creates a Route from one or more Behavior objects, unless a +block+ is
|
219
|
+
# passed in.
|
220
|
+
#
|
221
|
+
# ==== Parameters
|
222
|
+
# params<Hash>:: The parameters the route maps to.
|
223
|
+
# &block::
|
224
|
+
# Optional block. A new Behavior object is yielded and further #to
|
225
|
+
# operations may be called in the block.
|
226
|
+
#
|
227
|
+
# ==== Block parameters
|
228
|
+
# new_behavior<Behavior>:: The child behavior.
|
229
|
+
#
|
230
|
+
# ==== Returns
|
231
|
+
# Route:: It registers a new route and returns it.
|
232
|
+
#
|
233
|
+
# ==== Examples
|
234
|
+
# r.match('/:controller/:id).to(:action => 'show')
|
235
|
+
#
|
236
|
+
# r.to :controller => 'simple' do |s|
|
237
|
+
# s.match('/test').to(:action => 'index')
|
238
|
+
# s.match('/other').to(:action => 'other')
|
239
|
+
# end
|
240
|
+
#---
|
241
|
+
# @public
|
242
|
+
def to(params = {}, &block)
|
243
|
+
if block_given?
|
244
|
+
new_behavior = self.class.new({}, params, self)
|
245
|
+
yield new_behavior if block_given?
|
246
|
+
new_behavior
|
247
|
+
else
|
248
|
+
to_route(params).register
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Takes a block and stores it for deferred conditional routes. The block
|
253
|
+
# takes the +request+ object and the +params+ hash as parameters.
|
254
|
+
#
|
255
|
+
# ==== Parameters
|
256
|
+
# params<Hash>:: Parameters and conditions associated with this behavior.
|
257
|
+
# &conditional_block::
|
258
|
+
# A block with the conditions to be met for the behavior to take
|
259
|
+
# effect.
|
260
|
+
#
|
261
|
+
# ==== Returns
|
262
|
+
# Route :: The default route.
|
263
|
+
#
|
264
|
+
# ==== Examples
|
265
|
+
# r.defer_to do |request, params|
|
266
|
+
# params.merge :controller => 'here',
|
267
|
+
# :action => 'there' if request.xhr?
|
268
|
+
# end
|
269
|
+
#---
|
270
|
+
# @public
|
271
|
+
def defer_to(params = {}, &conditional_block)
|
272
|
+
to_route(params, &conditional_block).register
|
273
|
+
end
|
274
|
+
|
275
|
+
# Creates the most common routes /:controller/:action/:id.format when
|
276
|
+
# called with no arguments.
|
277
|
+
# You can pass a hash or a block to add parameters or override the default
|
278
|
+
# behavior.
|
279
|
+
#
|
280
|
+
# ==== Parameters
|
281
|
+
# params<Hash>::
|
282
|
+
# This optional hash can be used to augment the default settings
|
283
|
+
# &block::
|
284
|
+
# When passing a block a new behavior is yielded and more refinement is
|
285
|
+
# possible.
|
286
|
+
#
|
287
|
+
# ==== Returns
|
288
|
+
# Route:: the default route
|
289
|
+
#
|
290
|
+
# ==== Examples
|
291
|
+
#
|
292
|
+
# # Passing an extra parameter "mode" to all matches
|
293
|
+
# r.default_routes :mode => "default"
|
294
|
+
#
|
295
|
+
# # specifying exceptions within a block
|
296
|
+
# r.default_routes do |nr|
|
297
|
+
# nr.defer_to do |request, params|
|
298
|
+
# nr.match(:protocol => "http://").to(:controller => "login",
|
299
|
+
# :action => "new") if request.env["REQUEST_URI"] =~ /\/private\//
|
300
|
+
# end
|
301
|
+
# end
|
302
|
+
#---
|
303
|
+
# @public
|
304
|
+
def default_routes(params = {}, &block)
|
305
|
+
match(%r{/:controller(/:action(/:id)?)?(\.:format)?}).to(params, &block)
|
306
|
+
end
|
307
|
+
|
308
|
+
# Creates a namespace for a route. This way you can have logical
|
309
|
+
# separation to your routes.
|
310
|
+
#
|
311
|
+
# ==== Parameters
|
312
|
+
# name_or_path<String, Symbol>:: The name or path of the namespace.
|
313
|
+
# options<Hash>:: Optional hash, set :path if you want to override what appears on the url
|
314
|
+
# &block::
|
315
|
+
# A new Behavior instance is yielded in the block for nested resources.
|
316
|
+
#
|
317
|
+
# ==== Block parameters
|
318
|
+
# r<Behavior>:: The namespace behavior object.
|
319
|
+
#
|
320
|
+
# ==== Examples
|
321
|
+
# r.namespace :admin do |admin|
|
322
|
+
# admin.resources :accounts
|
323
|
+
# admin.resource :email
|
324
|
+
# end
|
325
|
+
#
|
326
|
+
# # /super_admin/accounts
|
327
|
+
# r.namespace(:admin, :path=>"super_admin") do |admin|
|
328
|
+
# admin.resources :accounts
|
329
|
+
# end
|
330
|
+
#---
|
331
|
+
# @public
|
332
|
+
def namespace(name_or_path, options={}, &block)
|
333
|
+
path = options[:path] || name_or_path.to_s
|
334
|
+
(path.empty? ? self : match("/#{path}")).to(:namespace => name_or_path.to_s) do |r|
|
335
|
+
yield r
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Behavior#+resources+ is a route helper for defining a collection of
|
340
|
+
# RESTful resources. It yields to a block for child routes.
|
341
|
+
#
|
342
|
+
# ==== Parameters
|
343
|
+
# name<String, Symbol>:: The name of the resources
|
344
|
+
# options<Hash>::
|
345
|
+
# Ovverides and parameters to be associated with the route
|
346
|
+
#
|
347
|
+
# ==== Options (options)
|
348
|
+
# :namespace<~to_s>: The namespace for this route.
|
349
|
+
# :name_prefix<~to_s>:
|
350
|
+
# A prefix for the named routes. If a namespace is passed and there
|
351
|
+
# isn't a name prefix, the namespace will become the prefix.
|
352
|
+
# :controller<~to_s>: The controller for this route
|
353
|
+
# :collection<~to_s>: Special settings for the collections routes
|
354
|
+
# :member<Hash>:
|
355
|
+
# Special settings and resources related to a specific member of this
|
356
|
+
# resource.
|
357
|
+
# :keys<Array>:
|
358
|
+
# A list of the keys to be used instead of :id with the resource in the order of the url.
|
359
|
+
#
|
360
|
+
# ==== Block parameters
|
361
|
+
# next_level<Behavior>:: The child behavior.
|
362
|
+
#
|
363
|
+
# ==== Returns
|
364
|
+
# Array::
|
365
|
+
# Routes which will define the specified RESTful collection of resources
|
366
|
+
#
|
367
|
+
# ==== Examples
|
368
|
+
#
|
369
|
+
# r.resources :posts # will result in the typical RESTful CRUD
|
370
|
+
# # lists resources
|
371
|
+
# # GET /posts/?(\.:format)? :action => "index"
|
372
|
+
# # GET /posts/index(\.:format)? :action => "index"
|
373
|
+
#
|
374
|
+
# # shows new resource form
|
375
|
+
# # GET /posts/new :action => "new"
|
376
|
+
#
|
377
|
+
# # creates resource
|
378
|
+
# # POST /posts/?(\.:format)?, :action => "create"
|
379
|
+
#
|
380
|
+
# # shows resource
|
381
|
+
# # GET /posts/:id(\.:format)? :action => "show"
|
382
|
+
#
|
383
|
+
# # shows edit form
|
384
|
+
# # GET /posts/:id/edit :action => "edit"
|
385
|
+
#
|
386
|
+
# # updates resource
|
387
|
+
# # PUT /posts/:id(\.:format)? :action => "update"
|
388
|
+
#
|
389
|
+
# # shows deletion confirmation page
|
390
|
+
# # GET /posts/:id/delete :action => "delete"
|
391
|
+
#
|
392
|
+
# # destroys resources
|
393
|
+
# # DELETE /posts/:id(\.:format)? :action => "destroy"
|
394
|
+
#
|
395
|
+
# # Nesting resources
|
396
|
+
# r.resources :posts do |posts|
|
397
|
+
# posts.resources :comments
|
398
|
+
# end
|
399
|
+
#---
|
400
|
+
# @public
|
401
|
+
def resources(name, options = {})
|
402
|
+
namespace = options[:namespace] || merged_params[:namespace]
|
403
|
+
|
404
|
+
next_level = match "/#{name}"
|
405
|
+
|
406
|
+
name_prefix = options.delete :name_prefix
|
407
|
+
matched_keys = options[:keys] ? options.delete(:keys).map{|k| ":#{k}"}.join("/") : ":id"
|
408
|
+
|
409
|
+
if name_prefix.nil? && !namespace.nil?
|
410
|
+
name_prefix = namespace_to_name_prefix namespace
|
411
|
+
end
|
412
|
+
|
413
|
+
unless @@parent_resources.empty?
|
414
|
+
parent_resource = namespace_to_name_prefix @@parent_resources.join('_')
|
415
|
+
end
|
416
|
+
|
417
|
+
options[:controller] ||= merged_params[:controller] || name.to_s
|
418
|
+
|
419
|
+
singular = name.to_s.singularize
|
420
|
+
|
421
|
+
route_plural_name = "#{name_prefix}#{parent_resource}#{name}"
|
422
|
+
route_singular_name = "#{name_prefix}#{parent_resource}#{singular}"
|
423
|
+
|
424
|
+
behaviors = []
|
425
|
+
|
426
|
+
if member = options.delete(:member)
|
427
|
+
member.each_pair do |action, methods|
|
428
|
+
behaviors << Behavior.new(
|
429
|
+
{ :path => %r{^/#{matched_keys}/#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
|
430
|
+
{ :action => action.to_s }, next_level
|
431
|
+
)
|
432
|
+
next_level.match("/#{matched_keys}/#{action}").to_route.name(:"#{action}_#{route_singular_name}")
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
if collection = options.delete(:collection)
|
437
|
+
collection.each_pair do |action, methods|
|
438
|
+
behaviors << Behavior.new(
|
439
|
+
{ :path => %r{^/#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
|
440
|
+
{ :action => action.to_s }, next_level
|
441
|
+
)
|
442
|
+
next_level.match("/#{action}").to_route.name(:"#{action}_#{route_plural_name}")
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
routes = many_behaviors_to(behaviors + next_level.send(:resources_behaviors, matched_keys), options)
|
447
|
+
|
448
|
+
|
449
|
+
|
450
|
+
# Add names to some routes
|
451
|
+
[['', :"#{route_plural_name}"],
|
452
|
+
["/#{matched_keys}", :"#{route_singular_name}"],
|
453
|
+
['/new', :"new_#{route_singular_name}"],
|
454
|
+
["/#{matched_keys}/edit", :"edit_#{route_singular_name}"],
|
455
|
+
["/#{matched_keys}/delete", :"delete_#{route_singular_name}"]
|
456
|
+
].each do |path,name|
|
457
|
+
next_level.match(path).to_route.name(name)
|
458
|
+
end
|
459
|
+
|
460
|
+
|
461
|
+
parent_keys = (matched_keys == ":id") ? ":#{singular}_id" : matched_keys
|
462
|
+
if block_given?
|
463
|
+
@@parent_resources.push(singular)
|
464
|
+
yield next_level.match("/#{parent_keys}")
|
465
|
+
@@parent_resources.pop
|
466
|
+
end
|
467
|
+
|
468
|
+
routes
|
469
|
+
end
|
470
|
+
|
471
|
+
# Behavior#+resource+ is a route helper for defining a singular RESTful
|
472
|
+
# resource. It yields to a block for child routes.
|
473
|
+
#
|
474
|
+
# ==== Parameters
|
475
|
+
# name<String, Symbol>:: The name of the resource.
|
476
|
+
# options<Hash>::
|
477
|
+
# Overides and parameters to be associated with the route.
|
478
|
+
#
|
479
|
+
# ==== Options (options)
|
480
|
+
# :namespace<~to_s>: The namespace for this route.
|
481
|
+
# :name_prefix<~to_s>:
|
482
|
+
# A prefix for the named routes. If a namespace is passed and there
|
483
|
+
# isn't a name prefix, the namespace will become the prefix.
|
484
|
+
# :controller<~to_s>: The controller for this route
|
485
|
+
#
|
486
|
+
# ==== Block parameters
|
487
|
+
# next_level<Behavior>:: The child behavior.
|
488
|
+
#
|
489
|
+
# ==== Returns
|
490
|
+
# Array:: Routes which define a RESTful single resource.
|
491
|
+
#
|
492
|
+
# ==== Examples
|
493
|
+
#
|
494
|
+
# r.resource :account # will result in the typical RESTful CRUD
|
495
|
+
# # shows new resource form
|
496
|
+
# # GET /account/new :action => "new"
|
497
|
+
#
|
498
|
+
# # creates resource
|
499
|
+
# # POST /account/?(\.:format)?, :action => "create"
|
500
|
+
#
|
501
|
+
# # shows resource
|
502
|
+
# # GET /account/(\.:format)? :action => "show"
|
503
|
+
#
|
504
|
+
# # shows edit form
|
505
|
+
# # GET /account//edit :action => "edit"
|
506
|
+
#
|
507
|
+
# # updates resource
|
508
|
+
# # PUT /account/(\.:format)? :action => "update"
|
509
|
+
#
|
510
|
+
# # shows deletion confirmation page
|
511
|
+
# # GET /account//delete :action => "delete"
|
512
|
+
#
|
513
|
+
# # destroys resources
|
514
|
+
# # DELETE /account/(\.:format)? :action => "destroy"
|
515
|
+
#
|
516
|
+
# You can optionally pass :namespace and :controller to refine the routing
|
517
|
+
# or pass a block to nest resources.
|
518
|
+
#
|
519
|
+
# r.resource :account, :namespace => "admin" do |account|
|
520
|
+
# account.resources :preferences, :controller => "settings"
|
521
|
+
# end
|
522
|
+
# ---
|
523
|
+
# @public
|
524
|
+
def resource(name, options = {})
|
525
|
+
namespace = options[:namespace] || merged_params[:namespace]
|
526
|
+
|
527
|
+
next_level = match "/#{name}"
|
528
|
+
|
529
|
+
options[:controller] ||= merged_params[:controller] || name.to_s
|
530
|
+
|
531
|
+
# Do not pass :name_prefix option on to to_resource
|
532
|
+
name_prefix = options.delete :name_prefix
|
533
|
+
|
534
|
+
if name_prefix.nil? && !namespace.nil?
|
535
|
+
name_prefix = namespace_to_name_prefix namespace
|
536
|
+
end
|
537
|
+
|
538
|
+
unless @@parent_resources.empty?
|
539
|
+
parent_resource = namespace_to_name_prefix @@parent_resources.join('_')
|
540
|
+
end
|
541
|
+
|
542
|
+
routes = next_level.to_resource options
|
543
|
+
|
544
|
+
route_name = "#{name_prefix}#{name}"
|
545
|
+
|
546
|
+
next_level.match('').to_route.name(:"#{route_name}")
|
547
|
+
next_level.match('/new').to_route.name(:"new_#{route_name}")
|
548
|
+
next_level.match('/edit').to_route.name(:"edit_#{route_name}")
|
549
|
+
next_level.match('/delete').to_route.name(:"delete_#{route_name}")
|
550
|
+
|
551
|
+
if block_given?
|
552
|
+
@@parent_resources.push(route_name)
|
553
|
+
yield next_level
|
554
|
+
@@parent_resources.pop
|
555
|
+
end
|
556
|
+
|
557
|
+
routes
|
558
|
+
end
|
559
|
+
|
560
|
+
# ==== Parameters
|
561
|
+
# params<Hash>:: Optional params for generating the RESTful routes.
|
562
|
+
# &block:: Optional block for the route generation.
|
563
|
+
#
|
564
|
+
# ==== Returns
|
565
|
+
# Array:: Routes matching the RESTful resource.
|
566
|
+
def to_resources(params = {}, &block)
|
567
|
+
many_behaviors_to resources_behaviors, params, &block
|
568
|
+
end
|
569
|
+
|
570
|
+
# ==== Parameters
|
571
|
+
# params<Hash>:: Optional params for generating the RESTful routes.
|
572
|
+
# &block:: Optional block for the route generation.
|
573
|
+
#
|
574
|
+
# ==== Returns
|
575
|
+
# Array:: Routes matching the RESTful singular resource.
|
576
|
+
def to_resource(params = {}, &block)
|
577
|
+
many_behaviors_to resource_behaviors, params, &block
|
578
|
+
end
|
579
|
+
|
580
|
+
# ==== Returns
|
581
|
+
# Hash::
|
582
|
+
# The original conditions of this behavior merged with the original
|
583
|
+
# conditions of all its ancestors.
|
584
|
+
def merged_original_conditions
|
585
|
+
if parent.nil?
|
586
|
+
@original_conditions
|
587
|
+
else
|
588
|
+
merged_so_far = parent.merged_original_conditions
|
589
|
+
if path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path])
|
590
|
+
merged_so_far.merge(@original_conditions).merge(:path => path)
|
591
|
+
else
|
592
|
+
merged_so_far.merge(@original_conditions)
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
# ==== Returns
|
598
|
+
# Hash::
|
599
|
+
# The conditions of this behavior merged with the conditions of all its
|
600
|
+
# ancestors.
|
601
|
+
def merged_conditions
|
602
|
+
if parent.nil?
|
603
|
+
@conditions
|
604
|
+
else
|
605
|
+
merged_so_far = parent.merged_conditions
|
606
|
+
if path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path])
|
607
|
+
merged_so_far.merge(@conditions).merge(:path => path)
|
608
|
+
else
|
609
|
+
merged_so_far.merge(@conditions)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
# ==== Returns
|
615
|
+
# Hash::
|
616
|
+
# The params of this behavior merged with the params of all its
|
617
|
+
# ancestors.
|
618
|
+
def merged_params
|
619
|
+
if parent.nil?
|
620
|
+
@params
|
621
|
+
else
|
622
|
+
parent.merged_params.merge(@params)
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
626
|
+
# ==== Returns
|
627
|
+
# Hash::
|
628
|
+
# The route placeholders, e.g. :controllers, of this behavior merged
|
629
|
+
# with the placeholders of all its ancestors.
|
630
|
+
def merged_placeholders
|
631
|
+
placeholders = {}
|
632
|
+
(ancestors.reverse + [self]).each do |a|
|
633
|
+
a.placeholders.each_pair do |k, pair|
|
634
|
+
param, place = pair
|
635
|
+
placeholders[k] = [param, place + (param == :path ? a.total_previous_captures : 0)]
|
636
|
+
end
|
637
|
+
end
|
638
|
+
placeholders
|
639
|
+
end
|
640
|
+
|
641
|
+
# ==== Returns
|
642
|
+
# String:: A human readable form of the behavior.
|
643
|
+
def inspect
|
644
|
+
"[captures: #{path_captures.inspect}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]"
|
645
|
+
end
|
646
|
+
|
647
|
+
# ==== Returns
|
648
|
+
# Boolean:: True if this behavior has a regexp.
|
649
|
+
def regexp?
|
650
|
+
@conditions_have_regexp
|
651
|
+
end
|
652
|
+
|
653
|
+
def redirect(url, permanent = true)
|
654
|
+
@redirects = true
|
655
|
+
@redirect_url = url
|
656
|
+
@redirect_status = permanent ? 301 : 302
|
657
|
+
|
658
|
+
# satisfy route compilation
|
659
|
+
self.to({})
|
660
|
+
end
|
661
|
+
|
662
|
+
def redirects?
|
663
|
+
@redirects
|
664
|
+
end
|
665
|
+
|
666
|
+
def ancestors
|
667
|
+
@ancestors ||= find_ancestors
|
668
|
+
end
|
669
|
+
|
670
|
+
protected
|
671
|
+
|
672
|
+
# ==== Parameters
|
673
|
+
# name_or_path<~to_s>::
|
674
|
+
# The name or path to convert to a form suitable for a prefix.
|
675
|
+
#
|
676
|
+
# ==== Returns
|
677
|
+
# String:: The prefix.
|
678
|
+
def namespace_to_name_prefix(name_or_path)
|
679
|
+
name_or_path.to_s.tr('/', '_') + '_'
|
680
|
+
end
|
681
|
+
|
682
|
+
# ==== Parameters
|
683
|
+
# matched_keys<String>::
|
684
|
+
# The keys to match
|
685
|
+
#
|
686
|
+
# ==== Returns
|
687
|
+
# Array:: Behaviors for a RESTful resource.
|
688
|
+
def resources_behaviors(matched_keys = ":id")
|
689
|
+
[
|
690
|
+
Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :get }, { :action => "index" }, self),
|
691
|
+
Behavior.new({ :path => %r[^/index(\.:format)?$], :method => :get }, { :action => "index" }, self),
|
692
|
+
Behavior.new({ :path => %r[^/new$], :method => :get }, { :action => "new" }, self),
|
693
|
+
Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :post }, { :action => "create" }, self),
|
694
|
+
Behavior.new({ :path => %r[^/#{matched_keys}(\.:format)?$], :method => :get }, { :action => "show" }, self),
|
695
|
+
Behavior.new({ :path => %r[^/#{matched_keys}/edit$], :method => :get }, { :action => "edit" }, self),
|
696
|
+
Behavior.new({ :path => %r[^/#{matched_keys}/delete$], :method => :get }, { :action => "delete" }, self),
|
697
|
+
Behavior.new({ :path => %r[^/#{matched_keys}(\.:format)?$], :method => :put }, { :action => "update" }, self),
|
698
|
+
Behavior.new({ :path => %r[^/#{matched_keys}(\.:format)?$], :method => :delete }, { :action => "destroy" }, self)
|
699
|
+
]
|
700
|
+
end
|
701
|
+
|
702
|
+
# ==== Parameters
|
703
|
+
# parent<Merb::Router::Behavior>::
|
704
|
+
# The parent behavior for the generated resource behaviors.
|
705
|
+
#
|
706
|
+
# ==== Returns
|
707
|
+
# Array:: Behaviors for a singular RESTful resource.
|
708
|
+
def resource_behaviors(parent = self)
|
709
|
+
[
|
710
|
+
Behavior.new({ :path => %r{^/new$}, :method => :get }, { :action => "new" }, parent),
|
711
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :post }, { :action => "create" }, parent),
|
712
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :get }, { :action => "show" }, parent),
|
713
|
+
Behavior.new({ :path => %r{^/edit$}, :method => :get }, { :action => "edit" }, parent),
|
714
|
+
Behavior.new({ :path => %r{^/delete$}, :method => :get }, { :action => "delete" }, parent),
|
715
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :put }, { :action => "update" }, parent),
|
716
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :delete }, { :action => "destroy" }, parent)
|
717
|
+
]
|
718
|
+
end
|
719
|
+
|
720
|
+
# ==== Parameters
|
721
|
+
# behaviors<Array>:: The behaviors to create routes from.
|
722
|
+
# params<Hash>:: Optional params for the route generation.
|
723
|
+
# &conditional_block:: Optional block for the route generation.
|
724
|
+
#
|
725
|
+
# ==== Returns
|
726
|
+
# Array:: The routes matching the behaviors.
|
727
|
+
def many_behaviors_to(behaviors, params = {}, &conditional_block)
|
728
|
+
behaviors.map { |b| b.to params, &conditional_block }
|
729
|
+
end
|
730
|
+
|
731
|
+
# Convert conditions to regular expression string sources for consistency.
|
732
|
+
def stringify_conditions
|
733
|
+
@conditions_have_regexp = false
|
734
|
+
@conditions.each_pair do |k,v|
|
735
|
+
# TODO: Other Regexp special chars
|
736
|
+
|
737
|
+
@conditions[k] = case v
|
738
|
+
when String,Symbol
|
739
|
+
"^#{v.to_s.escape_regexp}$"
|
740
|
+
when Regexp
|
741
|
+
@conditions_have_regexp = true
|
742
|
+
v.source
|
743
|
+
end
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
# Store the conditions as original conditions.
|
748
|
+
def copy_original_conditions
|
749
|
+
@original_conditions = {}
|
750
|
+
@conditions.each_pair do |key, value|
|
751
|
+
@original_conditions[key] = value.dup
|
752
|
+
end
|
753
|
+
@original_conditions
|
754
|
+
end
|
755
|
+
|
756
|
+
# Calculate the behaviors from the conditions and store them.
|
757
|
+
def deduce_placeholders
|
758
|
+
@conditions.each_pair do |match_key, source|
|
759
|
+
while match = SEGMENT_REGEXP.match(source)
|
760
|
+
source.sub! SEGMENT_REGEXP, PARENTHETICAL_SEGMENT_STRING
|
761
|
+
unless match[2] == ':' # No need to store anonymous place holders
|
762
|
+
placeholder_key = match[2].intern
|
763
|
+
@params[placeholder_key] = "#{match[1]}"
|
764
|
+
@placeholders[placeholder_key] = [
|
765
|
+
match_key, Behavior.count_parens_up_to(source, match.offset(1)[0])
|
766
|
+
]
|
767
|
+
end
|
768
|
+
end
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
# ==== Parameters
|
773
|
+
# list<Array>:: A list to which the ancestors should be added.
|
774
|
+
#
|
775
|
+
# ==== Returns
|
776
|
+
# Array:: All the ancestor behaviors of this behavior.
|
777
|
+
def find_ancestors(list = [])
|
778
|
+
if parent.nil?
|
779
|
+
list
|
780
|
+
else
|
781
|
+
list.push parent
|
782
|
+
parent.find_ancestors list
|
783
|
+
list
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
# ==== Returns
|
788
|
+
# Fixnum:: Number of regexp captures in the :path condition.
|
789
|
+
def path_captures
|
790
|
+
return 0 unless conditions[:path]
|
791
|
+
Behavior.count_parens_up_to(conditions[:path], conditions[:path].size)
|
792
|
+
end
|
793
|
+
|
794
|
+
# ==== Returns
|
795
|
+
# Fixnum:: Total number of previous path captures.
|
796
|
+
def total_previous_captures
|
797
|
+
ancestors.map{|a| a.path_captures}.inject(0){|sum, n| sum + n}
|
798
|
+
end
|
799
|
+
|
800
|
+
# def merge_with_ancestors
|
801
|
+
# self.class.new(merged_conditions, merged_params)
|
802
|
+
# end
|
803
|
+
|
804
|
+
# ==== Parameters
|
805
|
+
# conditions<Hash>::
|
806
|
+
# The conditions to compile. Defaults to merged_conditions.
|
807
|
+
#
|
808
|
+
# ==== Returns
|
809
|
+
# Hash:: The compiled conditions, with each value as a Regexp object.
|
810
|
+
def compiled_conditions(conditions = merged_conditions)
|
811
|
+
conditions.inject({}) do |compiled,(k,v)|
|
812
|
+
compiled.merge k => Regexp.new(v)
|
813
|
+
end
|
814
|
+
end
|
815
|
+
|
816
|
+
# ==== Parameters
|
817
|
+
# params<Hash>:: The params to compile. Defaults to merged_params.
|
818
|
+
# placeholders<Hash>::
|
819
|
+
# The route placeholders for this behavior. Defaults to
|
820
|
+
# merged_placeholders.
|
821
|
+
#
|
822
|
+
# ==== Returns
|
823
|
+
# String:: The params hash in an eval'able form.
|
824
|
+
#
|
825
|
+
# ==== Examples
|
826
|
+
# compiled_params({ :controller => "admin/:controller" })
|
827
|
+
# # => { :controller => "'admin/' + matches[:path][1]" }
|
828
|
+
#
|
829
|
+
def compiled_params(params = merged_params, placeholders = merged_placeholders)
|
830
|
+
compiled = {}
|
831
|
+
params.each_pair do |key, value|
|
832
|
+
unless value.is_a? String
|
833
|
+
raise ArgumentError, "param value for #{key.to_s} must be string (#{value.inspect})"
|
834
|
+
end
|
835
|
+
result = []
|
836
|
+
value = value.dup
|
837
|
+
match = true
|
838
|
+
while match
|
839
|
+
if match = SEGMENT_REGEXP_WITH_BRACKETS.match(value)
|
840
|
+
result << match.pre_match unless match.pre_match.empty?
|
841
|
+
ph_key = match[1][1..-1].intern
|
842
|
+
if match[2] # has brackets, e.g. :path[2]
|
843
|
+
result << :"#{ph_key}#{match[3]}"
|
844
|
+
else # no brackets, e.g. a named placeholder such as :controller
|
845
|
+
if place = placeholders[ph_key]
|
846
|
+
result << :"#{place[0]}#{place[1]}"
|
847
|
+
else
|
848
|
+
raise "Placeholder not found while compiling routes: :#{ph_key}"
|
849
|
+
end
|
850
|
+
end
|
851
|
+
value = match.post_match
|
852
|
+
elsif match = JUST_BRACKETS.match(value)
|
853
|
+
# This is a reference to :path
|
854
|
+
result << match.pre_match unless match.pre_match.empty?
|
855
|
+
result << :"path#{match[1]}"
|
856
|
+
value = match.post_match
|
857
|
+
else
|
858
|
+
result << value unless value.empty?
|
859
|
+
end
|
860
|
+
end
|
861
|
+
compiled[key] = Behavior.array_to_code(result).gsub("\\_", "_")
|
862
|
+
end
|
863
|
+
compiled
|
864
|
+
end
|
865
|
+
end # Behavior
|
866
|
+
end
|
867
|
+
end
|