thorero 0.9.4.4 → 0.9.4.5
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/LICENSE +1 -1
- data/README +21 -0
- data/Rakefile +275 -108
- 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/{extlib → merb-core}/logger.rb +2 -2
- 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 +493 -41
- data/README.txt +0 -3
- data/lib/extlib.rb +0 -32
- data/lib/extlib/assertions.rb +0 -8
- data/lib/extlib/blank.rb +0 -42
- data/lib/extlib/class.rb +0 -175
- data/lib/extlib/hash.rb +0 -410
- data/lib/extlib/hook.rb +0 -366
- data/lib/extlib/inflection.rb +0 -141
- data/lib/extlib/lazy_array.rb +0 -106
- data/lib/extlib/mash.rb +0 -143
- data/lib/extlib/module.rb +0 -37
- data/lib/extlib/object.rb +0 -165
- data/lib/extlib/object_space.rb +0 -13
- data/lib/extlib/pathname.rb +0 -5
- data/lib/extlib/pooling.rb +0 -233
- data/lib/extlib/rubygems.rb +0 -38
- data/lib/extlib/simple_set.rb +0 -39
- data/lib/extlib/string.rb +0 -132
- data/lib/extlib/struct.rb +0 -8
- data/lib/extlib/tasks/release.rb +0 -9
- data/lib/extlib/time.rb +0 -12
- data/lib/extlib/version.rb +0 -3
- data/lib/extlib/virtual_file.rb +0 -10
@@ -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
|