merb-core 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +136 -2
- data/CONTRIBUTORS +6 -0
- data/PUBLIC_CHANGELOG +38 -0
- data/Rakefile +38 -30
- data/lib/merb-core.rb +88 -23
- data/lib/merb-core/bootloader.rb +235 -51
- data/lib/merb-core/config.rb +121 -36
- data/lib/merb-core/controller/abstract_controller.rb +59 -36
- data/lib/merb-core/controller/exceptions.rb +2 -15
- data/lib/merb-core/controller/merb_controller.rb +44 -1
- data/lib/merb-core/controller/mime.rb +4 -0
- data/lib/merb-core/controller/mixins/controller.rb +38 -21
- data/lib/merb-core/controller/mixins/render.rb +44 -29
- data/lib/merb-core/controller/mixins/responder.rb +3 -31
- data/lib/merb-core/controller/template.rb +45 -21
- data/lib/merb-core/core_ext/kernel.rb +60 -32
- data/lib/merb-core/dispatch/default_exception/default_exception.rb +2 -2
- data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +3 -1
- data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +71 -67
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +7 -3
- data/lib/merb-core/dispatch/dispatcher.rb +3 -3
- data/lib/merb-core/dispatch/request.rb +56 -9
- data/lib/merb-core/dispatch/router.rb +159 -133
- data/lib/merb-core/dispatch/router/behavior.rb +462 -703
- data/lib/merb-core/dispatch/router/cached_proc.rb +3 -3
- data/lib/merb-core/dispatch/router/resources.rb +289 -0
- data/lib/merb-core/dispatch/router/route.rb +514 -294
- data/lib/merb-core/dispatch/session.rb +4 -2
- data/lib/merb-core/logger.rb +213 -202
- data/lib/merb-core/plugins.rb +9 -1
- data/lib/merb-core/rack.rb +3 -1
- data/lib/merb-core/rack/adapter.rb +7 -4
- data/lib/merb-core/rack/adapter/abstract.rb +188 -0
- data/lib/merb-core/rack/adapter/ebb.rb +12 -13
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +2 -15
- data/lib/merb-core/rack/adapter/irb.rb +3 -2
- data/lib/merb-core/rack/adapter/mongrel.rb +22 -15
- data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +4 -16
- data/lib/merb-core/rack/adapter/thin.rb +21 -22
- data/lib/merb-core/rack/adapter/thin_turbo.rb +4 -11
- data/lib/merb-core/rack/adapter/webrick.rb +54 -18
- data/lib/merb-core/rack/application.rb +4 -4
- data/lib/merb-core/rack/handler/mongrel.rb +12 -13
- data/lib/merb-core/rack/middleware/csrf.rb +1 -1
- data/lib/merb-core/rack/stream_wrapper.rb +41 -0
- data/lib/merb-core/server.rb +157 -90
- data/lib/merb-core/tasks/gem_management.rb +267 -0
- data/lib/merb-core/tasks/merb.rb +1 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +48 -34
- data/lib/merb-core/tasks/stats.rake +1 -1
- data/lib/merb-core/test.rb +9 -3
- data/lib/merb-core/test/helpers.rb +1 -0
- data/lib/merb-core/test/helpers/mock_request_helper.rb +393 -0
- data/lib/merb-core/test/helpers/multipart_request_helper.rb +3 -2
- data/lib/merb-core/test/helpers/request_helper.rb +40 -372
- data/lib/merb-core/test/helpers/route_helper.rb +16 -2
- data/lib/merb-core/test/matchers.rb +1 -0
- data/lib/merb-core/test/matchers/controller_matchers.rb +4 -247
- data/lib/merb-core/test/matchers/request_matchers.rb +140 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +22 -4
- data/lib/merb-core/test/run_specs.rb +118 -26
- data/lib/merb-core/test/test_ext/rspec.rb +181 -0
- data/lib/merb-core/version.rb +1 -9
- metadata +10 -342
- data/docs/bootloading.dox +0 -58
- data/docs/documentation_standards +0 -40
- 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 +0 -51
- data/lib/merb-core/vendor/facets.rb +0 -2
- data/lib/merb-core/vendor/facets/dictionary.rb +0 -433
- data/lib/merb-core/vendor/facets/inflect.rb +0 -342
- data/spec/private/config/adapter_spec.rb +0 -32
- data/spec/private/config/config_spec.rb +0 -202
- data/spec/private/config/environment_spec.rb +0 -13
- data/spec/private/config/merb_spec.rb +0 -34
- data/spec/private/config/spec_helper.rb +0 -1
- data/spec/private/core_ext/kernel_spec.rb +0 -159
- data/spec/private/dispatch/bootloader_spec.rb +0 -24
- data/spec/private/dispatch/fixture/app/controllers/application.rb +0 -4
- data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +0 -25
- data/spec/private/dispatch/fixture/app/controllers/foo.rb +0 -19
- data/spec/private/dispatch/fixture/app/helpers/global_helpers.rb +0 -8
- data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +0 -37
- data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +0 -216
- data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +0 -38
- data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +0 -40
- data/spec/private/dispatch/fixture/app/views/foo/bar.html.erb +0 -0
- data/spec/private/dispatch/fixture/app/views/layout/application.html.erb +0 -11
- data/spec/private/dispatch/fixture/config/black_hole.rb +0 -12
- data/spec/private/dispatch/fixture/config/environments/development.rb +0 -6
- data/spec/private/dispatch/fixture/config/environments/production.rb +0 -5
- data/spec/private/dispatch/fixture/config/environments/test.rb +0 -6
- data/spec/private/dispatch/fixture/config/init.rb +0 -45
- data/spec/private/dispatch/fixture/config/rack.rb +0 -11
- data/spec/private/dispatch/fixture/config/router.rb +0 -35
- data/spec/private/dispatch/fixture/log/merb_test.log +0 -8839
- data/spec/private/dispatch/fixture/public/images/merb.jpg +0 -0
- data/spec/private/dispatch/fixture/public/merb.fcgi +0 -4
- data/spec/private/dispatch/fixture/public/stylesheets/master.css +0 -119
- data/spec/private/dispatch/route_params_spec.rb +0 -23
- data/spec/private/dispatch/spec_helper.rb +0 -1
- data/spec/private/router/behavior_spec.rb +0 -60
- data/spec/private/router/fixture/log/merb_test.log +0 -35
- data/spec/private/router/route_spec.rb +0 -418
- data/spec/private/router/router_spec.rb +0 -183
- data/spec/private/vendor/facets/plural_spec.rb +0 -564
- data/spec/private/vendor/facets/singular_spec.rb +0 -489
- data/spec/public/DEFINITIONS +0 -11
- data/spec/public/abstract_controller/controllers/alt_views/layout/application.erb +0 -1
- data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_string_controller_layout.erb +0 -1
- data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_template_controller_layout.erb +0 -1
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/show.erb +0 -1
- data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/alt_views/partial/basic_partial_with_multiple_roots/_partial.erb +0 -1
- data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_and_custom_location/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_inherited/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/cousins.rb +0 -41
- data/spec/public/abstract_controller/controllers/display.rb +0 -60
- data/spec/public/abstract_controller/controllers/filters.rb +0 -260
- data/spec/public/abstract_controller/controllers/helpers.rb +0 -41
- data/spec/public/abstract_controller/controllers/partial.rb +0 -121
- data/spec/public/abstract_controller/controllers/render.rb +0 -113
- data/spec/public/abstract_controller/controllers/views/helpers/capture/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/helpers/capture_eq/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/helpers/capture_with_args/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/helpers/concat/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/layout/alt.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/layout/custom.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object_with_action/new.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template/index.erb +0 -1
- 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 +0 -1
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/show.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_two_throw_contents/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/another_directory/_partial.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial/_partial.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/basic_partial_with_multiple_roots/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_first.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_second.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/nested_partial/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_in_another_directory/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/_collection.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/_collection.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/_collection.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/_collection.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_counter/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/_variables.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/_both.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/_partial.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/with_absolute_partial/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/_with_partial.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/_with_partial.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/with_partial/_with_partial.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/partial/with_partial/index.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/test_display/foo.html.erb +0 -1
- data/spec/public/abstract_controller/controllers/views/test_render/foo.html.erb +0 -0
- data/spec/public/abstract_controller/controllers/views/wonderful/index.erb +0 -1
- data/spec/public/abstract_controller/display_spec.rb +0 -37
- data/spec/public/abstract_controller/filter_spec.rb +0 -136
- data/spec/public/abstract_controller/helper_spec.rb +0 -21
- data/spec/public/abstract_controller/partial_spec.rb +0 -61
- data/spec/public/abstract_controller/render_spec.rb +0 -90
- data/spec/public/abstract_controller/spec_helper.rb +0 -31
- data/spec/public/boot_loader/boot_loader_spec.rb +0 -33
- data/spec/public/boot_loader/spec_helper.rb +0 -1
- data/spec/public/controller/authentication_spec.rb +0 -174
- data/spec/public/controller/base_spec.rb +0 -88
- data/spec/public/controller/conditional_get_spec.rb +0 -100
- data/spec/public/controller/config/init.rb +0 -6
- data/spec/public/controller/controllers/authentication.rb +0 -74
- data/spec/public/controller/controllers/base.rb +0 -65
- data/spec/public/controller/controllers/conditional_get.rb +0 -35
- data/spec/public/controller/controllers/cookies.rb +0 -36
- data/spec/public/controller/controllers/dispatcher.rb +0 -35
- data/spec/public/controller/controllers/display.rb +0 -118
- data/spec/public/controller/controllers/redirect.rb +0 -36
- data/spec/public/controller/controllers/responder.rb +0 -93
- data/spec/public/controller/controllers/url.rb +0 -7
- data/spec/public/controller/controllers/views/layout/custom.html.erb +0 -1
- data/spec/public/controller/controllers/views/layout/custom_arg.html.erb +0 -1
- data/spec/public/controller/controllers/views/layout/custom_arg.json.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_and_local_provides/index.xml.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.xml.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/index.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/no_layout.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template_argument/index.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/html_default/index.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/layout/custom.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.xml.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.html.erb +0 -1
- data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.js.erb +0 -1
- data/spec/public/controller/cookies_spec.rb +0 -95
- data/spec/public/controller/dispatcher_spec.rb +0 -410
- data/spec/public/controller/display_spec.rb +0 -84
- data/spec/public/controller/redirect_spec.rb +0 -33
- data/spec/public/controller/responder_spec.rb +0 -169
- data/spec/public/controller/spec_helper.rb +0 -13
- data/spec/public/controller/url_spec.rb +0 -255
- data/spec/public/core/merb_core_spec.rb +0 -45
- data/spec/public/core_ext/fixtures/core_ext_dependency.rb +0 -2
- data/spec/public/core_ext/kernel_spec.rb +0 -88
- data/spec/public/core_ext/spec_helper.rb +0 -1
- data/spec/public/directory_structure/directory/app/controllers/application.rb +0 -3
- data/spec/public/directory_structure/directory/app/controllers/base.rb +0 -13
- data/spec/public/directory_structure/directory/app/controllers/custom.rb +0 -19
- data/spec/public/directory_structure/directory/app/views/base/template.html.erb +0 -1
- data/spec/public/directory_structure/directory/app/views/wonderful/template.erb +0 -1
- data/spec/public/directory_structure/directory/config/router.rb +0 -3
- data/spec/public/directory_structure/directory/log/merb_test.log +0 -4588
- data/spec/public/directory_structure/directory_spec.rb +0 -43
- data/spec/public/logger/logger_spec.rb +0 -181
- data/spec/public/logger/spec_helper.rb +0 -1
- data/spec/public/rack/conditinal_get_middleware_spec.rb +0 -127
- data/spec/public/rack/csrf_middleware_spec.rb +0 -70
- data/spec/public/rack/rack_middleware_spec.rb +0 -99
- data/spec/public/rack/shared_example_groups.rb +0 -35
- data/spec/public/reloading/directory/app/controllers/application.rb +0 -3
- data/spec/public/reloading/directory/app/controllers/reload.rb +0 -6
- data/spec/public/reloading/directory/config/init.rb +0 -2
- data/spec/public/reloading/directory/log/merb_test.log +0 -288231
- data/spec/public/reloading/reload_spec.rb +0 -103
- data/spec/public/request/multipart_spec.rb +0 -41
- data/spec/public/request/request_spec.rb +0 -250
- data/spec/public/router/default_spec.rb +0 -21
- data/spec/public/router/deferred_spec.rb +0 -22
- data/spec/public/router/fixation_spec.rb +0 -49
- data/spec/public/router/fixture/log/merb_test.log +0 -524
- data/spec/public/router/namespace_spec.rb +0 -113
- data/spec/public/router/nested_matches_spec.rb +0 -97
- data/spec/public/router/nested_resources_spec.rb +0 -41
- data/spec/public/router/resource_spec.rb +0 -37
- data/spec/public/router/resources_spec.rb +0 -82
- data/spec/public/router/spec_helper.rb +0 -90
- data/spec/public/router/special_spec.rb +0 -61
- data/spec/public/router/string_spec.rb +0 -61
- data/spec/public/session/controllers/sessions.rb +0 -56
- data/spec/public/session/cookie_session_spec.rb +0 -73
- data/spec/public/session/memcached_session_spec.rb +0 -31
- data/spec/public/session/memory_session_spec.rb +0 -28
- data/spec/public/session/multiple_sessions_spec.rb +0 -74
- data/spec/public/session/no_session_spec.rb +0 -12
- data/spec/public/session/session_spec.rb +0 -106
- data/spec/public/template/template_spec.rb +0 -104
- data/spec/public/template/templates/error.html.erb +0 -2
- data/spec/public/template/templates/template.html.erb +0 -1
- data/spec/public/template/templates/template.html.myt +0 -1
- data/spec/public/test/controller_matchers_spec.rb +0 -412
- data/spec/public/test/controllers/controller_assertion_mock.rb +0 -7
- data/spec/public/test/controllers/dispatch_controller.rb +0 -11
- data/spec/public/test/controllers/spec_helper_controller.rb +0 -39
- data/spec/public/test/multipart_request_helper_spec.rb +0 -159
- data/spec/public/test/multipart_upload_text_file.txt +0 -1
- data/spec/public/test/request_helper_spec.rb +0 -269
- data/spec/public/test/route_helper_spec.rb +0 -78
- data/spec/public/test/route_matchers_spec.rb +0 -166
- data/spec/public/test/view_helper_spec.rb +0 -96
- data/spec/public/test/view_matchers_spec.rb +0 -183
- data/spec/spec_helper.rb +0 -121
@@ -5,7 +5,7 @@ module Merb
|
|
5
5
|
class CachedProc
|
6
6
|
@@index = 0
|
7
7
|
@@list = []
|
8
|
-
|
8
|
+
|
9
9
|
attr_accessor :cache, :index
|
10
10
|
|
11
11
|
# ==== Parameters
|
@@ -13,13 +13,13 @@ module Merb
|
|
13
13
|
def initialize(cache)
|
14
14
|
@cache, @index = cache, CachedProc.register(self)
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
# ==== Returns
|
18
18
|
# String:: The CachedProc object in a format embeddable within a string.
|
19
19
|
def to_s
|
20
20
|
"CachedProc[#{@index}].cache"
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
class << self
|
24
24
|
|
25
25
|
# ==== Parameters
|
@@ -0,0 +1,289 @@
|
|
1
|
+
module Merb
|
2
|
+
class Router
|
3
|
+
|
4
|
+
module Resources
|
5
|
+
# Behavior#+resources+ is a route helper for defining a collection of
|
6
|
+
# RESTful resources. It yields to a block for child routes.
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# name<String, Symbol>:: The name of the resources
|
10
|
+
# options<Hash>::
|
11
|
+
# Ovverides and parameters to be associated with the route
|
12
|
+
#
|
13
|
+
# ==== Options (options)
|
14
|
+
# :namespace<~to_s>: The namespace for this route.
|
15
|
+
# :name_prefix<~to_s>:
|
16
|
+
# A prefix for the named routes. If a namespace is passed and there
|
17
|
+
# isn't a name prefix, the namespace will become the prefix.
|
18
|
+
# :controller<~to_s>: The controller for this route
|
19
|
+
# :collection<~to_s>: Special settings for the collections routes
|
20
|
+
# :member<Hash>:
|
21
|
+
# Special settings and resources related to a specific member of this
|
22
|
+
# resource.
|
23
|
+
# :keys<Array>:
|
24
|
+
# A list of the keys to be used instead of :id with the resource in the order of the url.
|
25
|
+
# :singular<Symbol>
|
26
|
+
#
|
27
|
+
# ==== Block parameters
|
28
|
+
# next_level<Behavior>:: The child behavior.
|
29
|
+
#
|
30
|
+
# ==== Returns
|
31
|
+
# Array::
|
32
|
+
# Routes which will define the specified RESTful collection of resources
|
33
|
+
#
|
34
|
+
# ==== Examples
|
35
|
+
#
|
36
|
+
# r.resources :posts # will result in the typical RESTful CRUD
|
37
|
+
# # lists resources
|
38
|
+
# # GET /posts/?(\.:format)? :action => "index"
|
39
|
+
# # GET /posts/index(\.:format)? :action => "index"
|
40
|
+
#
|
41
|
+
# # shows new resource form
|
42
|
+
# # GET /posts/new :action => "new"
|
43
|
+
#
|
44
|
+
# # creates resource
|
45
|
+
# # POST /posts/?(\.:format)?, :action => "create"
|
46
|
+
#
|
47
|
+
# # shows resource
|
48
|
+
# # GET /posts/:id(\.:format)? :action => "show"
|
49
|
+
#
|
50
|
+
# # shows edit form
|
51
|
+
# # GET /posts/:id/edit :action => "edit"
|
52
|
+
#
|
53
|
+
# # updates resource
|
54
|
+
# # PUT /posts/:id(\.:format)? :action => "update"
|
55
|
+
#
|
56
|
+
# # shows deletion confirmation page
|
57
|
+
# # GET /posts/:id/delete :action => "delete"
|
58
|
+
#
|
59
|
+
# # destroys resources
|
60
|
+
# # DELETE /posts/:id(\.:format)? :action => "destroy"
|
61
|
+
#
|
62
|
+
# # Nesting resources
|
63
|
+
# r.resources :posts do |posts|
|
64
|
+
# posts.resources :comments
|
65
|
+
# end
|
66
|
+
#---
|
67
|
+
# @public
|
68
|
+
def resources(name, *args, &block)
|
69
|
+
name = name.to_s
|
70
|
+
options = extract_options_from_args!(args) || {}
|
71
|
+
singular = options[:singular] ? options[:singular].to_s : Extlib::Inflection.singularize(name)
|
72
|
+
klass = args.first ? args.first.to_s : Extlib::Inflection.classify(singular)
|
73
|
+
keys = [ options.delete(:keys) || options.delete(:key) || :id ].flatten
|
74
|
+
params = { :controller => options.delete(:controller) || name }
|
75
|
+
collection = options.delete(:collection) || {}
|
76
|
+
member = { :edit => :get, :delete => :get }.merge(options.delete(:member) || {})
|
77
|
+
|
78
|
+
# Try pulling :namespace out of options for backwards compatibility
|
79
|
+
options[:name_prefix] ||= nil # Don't use a name_prefix if not needed
|
80
|
+
options[:resource_prefix] ||= nil # Don't use a resource_prefix if not needed
|
81
|
+
options[:controller_prefix] ||= options.delete(:namespace)
|
82
|
+
|
83
|
+
self.namespace(name, options).to(params) do |resource|
|
84
|
+
root_keys = keys.map { |k| ":#{k}" }.join("/")
|
85
|
+
|
86
|
+
# => index
|
87
|
+
resource.match("(/index)(.:format)", :method => :get).to(:action => "index").
|
88
|
+
name(name).register_resource(name)
|
89
|
+
|
90
|
+
# => create
|
91
|
+
resource.match("(.:format)", :method => :post).to(:action => "create")
|
92
|
+
|
93
|
+
# => new
|
94
|
+
resource.match("/new(.:format)", :method => :get).to(:action => "new").
|
95
|
+
name("new", singular).register_resource(name, "new")
|
96
|
+
|
97
|
+
# => user defined collection routes
|
98
|
+
collection.each_pair do |action, method|
|
99
|
+
action = action.to_s
|
100
|
+
resource.match("/#{action}(.:format)", :method => method).to(:action => "#{action}").
|
101
|
+
name(action, name).register_resource(name, action)
|
102
|
+
end
|
103
|
+
|
104
|
+
# => show
|
105
|
+
resource.match("/#{root_keys}(.:format)", :method => :get).to(:action => "show").
|
106
|
+
name(singular).register_resource(klass)
|
107
|
+
|
108
|
+
# => user defined member routes
|
109
|
+
member.each_pair do |action, method|
|
110
|
+
action = action.to_s
|
111
|
+
resource.match("/#{root_keys}/#{action}(.:format)", :method => method).
|
112
|
+
to(:action => "#{action}").name(action, singular).register_resource(klass, action)
|
113
|
+
end
|
114
|
+
|
115
|
+
# => update
|
116
|
+
resource.match("/#{root_keys}(.:format)", :method => :put).
|
117
|
+
to(:action => "update")
|
118
|
+
|
119
|
+
# => destroy
|
120
|
+
resource.match("/#{root_keys}(.:format)", :method => :delete).
|
121
|
+
to(:action => "destroy")
|
122
|
+
|
123
|
+
if block_given?
|
124
|
+
nested_keys = keys.map do |k|
|
125
|
+
k.to_s == "id" ? ":#{singular}_id" : ":#{k}"
|
126
|
+
end.join("/")
|
127
|
+
|
128
|
+
# Procs for building the extra collection/member resource routes
|
129
|
+
placeholder = Router.resource_routes[ [@options[:resource_prefix], klass].flatten.compact ]
|
130
|
+
builders = {}
|
131
|
+
|
132
|
+
builders[:collection] = lambda do |action, to, method|
|
133
|
+
resource.before(placeholder).match("/#{action}(.:format)", :method => method).
|
134
|
+
to(:action => to).name(action, name).register_resource(name, action)
|
135
|
+
end
|
136
|
+
|
137
|
+
builders[:member] = lambda do |action, to, method|
|
138
|
+
resource.match("/#{root_keys}/#{action}(.:format)", :method => method).
|
139
|
+
to(:action => to).name(action, singular).register_resource(klass, action)
|
140
|
+
end
|
141
|
+
|
142
|
+
resource.options(:name_prefix => singular, :resource_prefix => klass).
|
143
|
+
match("/#{nested_keys}").resource_block(builders, &block)
|
144
|
+
end
|
145
|
+
end # namespace
|
146
|
+
end # resources
|
147
|
+
|
148
|
+
# Behavior#+resource+ is a route helper for defining a singular RESTful
|
149
|
+
# resource. It yields to a block for child routes.
|
150
|
+
#
|
151
|
+
# ==== Parameters
|
152
|
+
# name<String, Symbol>:: The name of the resource.
|
153
|
+
# options<Hash>::
|
154
|
+
# Overides and parameters to be associated with the route.
|
155
|
+
#
|
156
|
+
# ==== Options (options)
|
157
|
+
# :namespace<~to_s>: The namespace for this route.
|
158
|
+
# :name_prefix<~to_s>:
|
159
|
+
# A prefix for the named routes. If a namespace is passed and there
|
160
|
+
# isn't a name prefix, the namespace will become the prefix.
|
161
|
+
# :controller<~to_s>: The controller for this route
|
162
|
+
#
|
163
|
+
# ==== Block parameters
|
164
|
+
# next_level<Behavior>:: The child behavior.
|
165
|
+
#
|
166
|
+
# ==== Returns
|
167
|
+
# Array:: Routes which define a RESTful single resource.
|
168
|
+
#
|
169
|
+
# ==== Examples
|
170
|
+
#
|
171
|
+
# r.resource :account # will result in the typical RESTful CRUD
|
172
|
+
# # shows new resource form
|
173
|
+
# # GET /account/new :action => "new"
|
174
|
+
#
|
175
|
+
# # creates resource
|
176
|
+
# # POST /account/?(\.:format)?, :action => "create"
|
177
|
+
#
|
178
|
+
# # shows resource
|
179
|
+
# # GET /account/(\.:format)? :action => "show"
|
180
|
+
#
|
181
|
+
# # shows edit form
|
182
|
+
# # GET /account//edit :action => "edit"
|
183
|
+
#
|
184
|
+
# # updates resource
|
185
|
+
# # PUT /account/(\.:format)? :action => "update"
|
186
|
+
#
|
187
|
+
# # shows deletion confirmation page
|
188
|
+
# # GET /account//delete :action => "delete"
|
189
|
+
#
|
190
|
+
# # destroys resources
|
191
|
+
# # DELETE /account/(\.:format)? :action => "destroy"
|
192
|
+
#
|
193
|
+
# You can optionally pass :namespace and :controller to refine the routing
|
194
|
+
# or pass a block to nest resources.
|
195
|
+
#
|
196
|
+
# r.resource :account, :namespace => "admin" do |account|
|
197
|
+
# account.resources :preferences, :controller => "settings"
|
198
|
+
# end
|
199
|
+
# ---
|
200
|
+
# @public
|
201
|
+
def resource(name, *args, &block)
|
202
|
+
name = name.to_s
|
203
|
+
options = extract_options_from_args!(args) || {}
|
204
|
+
params = { :controller => options.delete(:controller) || name.pluralize }
|
205
|
+
member = { :new => :get, :edit => :get, :delete => :get }.merge(options.delete(:member) || {})
|
206
|
+
|
207
|
+
options[:name_prefix] ||= nil # Don't use a name_prefix if not needed
|
208
|
+
options[:resource_prefix] ||= nil # Don't use a resource_prefix if not needed
|
209
|
+
options[:controller_prefix] ||= options.delete(:namespace)
|
210
|
+
|
211
|
+
self.namespace(name, options).to(params) do |resource|
|
212
|
+
# => show
|
213
|
+
resource.match("(.:format)", :method => :get).to(:action => "show").
|
214
|
+
name(name).register_resource(name)
|
215
|
+
|
216
|
+
# => create
|
217
|
+
resource.match("(.:format)", :method => :post).to(:action => "create")
|
218
|
+
|
219
|
+
# => update
|
220
|
+
resource.match("(.:format)", :method => :put).to(:action => "update")
|
221
|
+
|
222
|
+
# => destroy
|
223
|
+
resource.match("(.:format)", :method => :delete).to(:action => "destroy")
|
224
|
+
|
225
|
+
member.each_pair do |action, method|
|
226
|
+
action = action.to_s
|
227
|
+
resource.match("/#{action}(.:format)", :method => method).to(:action => action).
|
228
|
+
name(action, name).register_resource(name, action)
|
229
|
+
end
|
230
|
+
|
231
|
+
if block_given?
|
232
|
+
builders = {}
|
233
|
+
|
234
|
+
builders[:member] = lambda do |action, to, method|
|
235
|
+
resource.match("/#{action}(.:format)", :method => method).to(:action => to).
|
236
|
+
name(action, name).register_resource(name, action)
|
237
|
+
end
|
238
|
+
|
239
|
+
resource.options(:name_prefix => name, :resource_prefix => name).
|
240
|
+
resource_block(builders, &block)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
protected
|
246
|
+
|
247
|
+
def register_resource(*key)
|
248
|
+
key = [@options[:resource_prefix], key].flatten.compact
|
249
|
+
@route.resource = key
|
250
|
+
self
|
251
|
+
end
|
252
|
+
|
253
|
+
def resource_block(builders, &block)
|
254
|
+
behavior = ResourceBehavior.new(builders, @proxy, @conditions, @params, @defaults, @identifiers, @options, @blocks)
|
255
|
+
with_behavior_context(behavior, &block)
|
256
|
+
end
|
257
|
+
|
258
|
+
end # Resources
|
259
|
+
|
260
|
+
class Behavior
|
261
|
+
include Resources
|
262
|
+
end
|
263
|
+
|
264
|
+
# Adding the collection and member methods to behavior
|
265
|
+
class ResourceBehavior < Behavior #:nodoc:
|
266
|
+
|
267
|
+
def initialize(builders, *args)
|
268
|
+
super(*args)
|
269
|
+
@collection = builders[:collection]
|
270
|
+
@member = builders[:member]
|
271
|
+
end
|
272
|
+
|
273
|
+
def collection(action, options = {})
|
274
|
+
action = action.to_s
|
275
|
+
method = options[:method]
|
276
|
+
to = options[:to] || action
|
277
|
+
@collection[action, to, method]
|
278
|
+
end
|
279
|
+
|
280
|
+
def member(action, options = {})
|
281
|
+
action = action.to_s
|
282
|
+
method = options[:method]
|
283
|
+
to = options[:to] || action
|
284
|
+
@member[action, to, method]
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -1,348 +1,568 @@
|
|
1
|
-
require 'merb-core/controller/mixins/responder'
|
2
1
|
module Merb
|
3
2
|
|
4
3
|
class Router
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# of Merb::Router.match method.
|
29
|
-
# Condition statements are Ruby code in form of string.
|
30
|
-
#
|
31
|
-
# ==== Segments.
|
32
|
-
#
|
33
|
-
# Route definitions use conventional syntax for named parameters.
|
34
|
-
# This splits route path into segments. Static (not changing) segments
|
35
|
-
# represented internally as strings, named parameters are stored
|
36
|
-
# as symbols and called symbol segments. Symbol segments
|
37
|
-
# map to groups in regular expression in resulting condition statement.
|
38
|
-
#
|
39
|
-
# ==== Route conditions.
|
40
|
-
#
|
41
|
-
# Because route conditions include path matching,
|
42
|
-
# regular expression is created from string that uses
|
43
|
-
# :segment format to fetch groups and assign them to
|
44
|
-
# named parameters. This regular expression is used
|
45
|
-
# to produce compiled statement mentioned above.
|
46
|
-
#
|
47
|
-
# Route conditions may also include
|
48
|
-
# user agent. Symbol segments
|
49
|
-
#
|
50
|
-
# Here is example of Route conditions:
|
51
|
-
# {
|
52
|
-
# :path => /^\/continents\/?(\.([^\/.,;?]+))?$/,
|
53
|
-
# :method => /^get$/
|
54
|
-
# }
|
55
|
-
#
|
56
|
-
#
|
57
|
-
# ==== Route parameters.
|
58
|
-
#
|
59
|
-
# Route parameters is a Hash with controller name,
|
60
|
-
# action name and parameters key/value pairs.
|
61
|
-
# It is then merged with request.params hash.
|
62
|
-
#
|
63
|
-
# Example of route parameters:
|
64
|
-
#
|
65
|
-
# {
|
66
|
-
# :action => "\"index\"",
|
67
|
-
# :format => "path2",
|
68
|
-
# :controller => "\"continents\""
|
69
|
-
# }
|
70
|
-
#
|
71
|
-
# Router takes first matching route and uses it's parameters
|
72
|
-
# to dispatch request to certain controller and action.
|
73
|
-
#
|
74
|
-
# ==== Behavior
|
75
|
-
#
|
76
|
-
# Each route has utility collaborator called behavior
|
77
|
-
# that incapsulates additional information about route
|
78
|
-
# (like namespace or if route is deferred) and also
|
79
|
-
# provides utility methods.
|
80
|
-
#
|
81
|
-
# ==== Route registration.
|
82
|
-
#
|
83
|
-
# When route is added to set of routes, it is called route
|
84
|
-
# registration. Registred route knows it's index in routes set.
|
85
|
-
#
|
86
|
-
# ==== Fixation
|
87
|
-
# Fixatable routes allow setting of session key from GET params
|
88
|
-
# found in incoming request. This is very useful to allow certain
|
89
|
-
# URLs to be used by rich media applications and other kinds
|
90
|
-
# of clients that have no other way of passing session identifier.
|
91
|
-
#
|
92
|
-
# ==== Conditional block.
|
93
|
-
# Conditional block is anonymous function that is evaluated
|
94
|
-
# when deferred routes are processed. Unless route is deferred,
|
95
|
-
# it has no condition block.
|
96
|
-
class Route
|
97
|
-
attr_reader :conditions, :conditional_block
|
98
|
-
attr_reader :params, :behavior, :segments, :index, :symbol
|
99
|
-
|
100
|
-
# ==== Parameters
|
101
|
-
# conditions<Hash>:: Conditions for the route.
|
102
|
-
# params<Hash>:: Parameters for the route.
|
103
|
-
# behavior<Merb::Router::Behavior>::
|
104
|
-
# The associated behavior. Defaults to nil.
|
105
|
-
# &conditional_block::
|
106
|
-
# A block with the conditions to be met for the route to take effect.
|
107
|
-
def initialize(conditions, params, behavior = nil, &conditional_block)
|
108
|
-
@conditions, @params, @behavior = conditions, params, behavior
|
109
|
-
@conditional_block = conditional_block
|
110
|
-
@fixation=false
|
111
|
-
if @behavior && (path = @behavior.merged_original_conditions[:path])
|
112
|
-
@segments = segments_from_path(path)
|
4
|
+
# This entire class is private and should never be accessed outside of
|
5
|
+
# Merb::Router and Behavior
|
6
|
+
class Route #:nodoc:
|
7
|
+
SEGMENT_REGEXP = /(:([a-z](_?[a-z0-9])*))/
|
8
|
+
OPTIONAL_SEGMENT_REGEX = /^.*?([\(\)])/i
|
9
|
+
SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
|
10
|
+
JUST_BRACKETS = /\[(\d+)\]/
|
11
|
+
SEGMENT_CHARACTERS = "[^\/.,;?]".freeze
|
12
|
+
|
13
|
+
attr_reader :conditions, :params, :segments
|
14
|
+
attr_reader :index, :variables, :name
|
15
|
+
attr_accessor :fixation
|
16
|
+
|
17
|
+
def initialize(conditions, params, deferred_procs, options = {})
|
18
|
+
@conditions, @params = conditions, params
|
19
|
+
|
20
|
+
if options[:redirects]
|
21
|
+
@redirects = true
|
22
|
+
@redirect_status = @params[:status]
|
23
|
+
@redirect_url = @params[:url]
|
24
|
+
@defaults = {}
|
25
|
+
else
|
26
|
+
@defaults = options[:defaults] || {}
|
113
27
|
end
|
28
|
+
|
29
|
+
# @conditional_block = conditional_block
|
30
|
+
|
31
|
+
@identifiers = options[:identifiers]
|
32
|
+
@deferred_procs = deferred_procs
|
33
|
+
@segments = []
|
34
|
+
@symbol_conditions = {}
|
35
|
+
@placeholders = {}
|
36
|
+
compile
|
114
37
|
end
|
115
38
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
39
|
+
def regexp?
|
40
|
+
@regexp
|
41
|
+
end
|
42
|
+
|
43
|
+
def allow_fixation?
|
44
|
+
@fixation
|
121
45
|
end
|
122
46
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
behavior.redirect_status
|
47
|
+
def to_s
|
48
|
+
regexp? ?
|
49
|
+
"/#{conditions[:path].source}/" :
|
50
|
+
segment_level_to_s(segments)
|
128
51
|
end
|
129
52
|
|
130
|
-
|
131
|
-
|
132
|
-
#
|
133
|
-
def
|
134
|
-
|
53
|
+
alias_method :inspect, :to_s
|
54
|
+
|
55
|
+
# Appends self to Merb::Router.routes
|
56
|
+
def register
|
57
|
+
@index = Merb::Router.routes.size
|
58
|
+
Merb::Router.routes << self
|
59
|
+
self
|
135
60
|
end
|
136
61
|
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
|
62
|
+
# Inserts self to Merb::Router.routes at the specified index.
|
63
|
+
def register_at(index)
|
64
|
+
@index = index
|
65
|
+
Merb::Router.routes.insert(index, self)
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sets the route as a resource route with the given key as the
|
70
|
+
# lookup key.
|
71
|
+
def resource=(key)
|
72
|
+
Router.resource_routes[key] = self
|
73
|
+
key
|
74
|
+
end
|
75
|
+
|
76
|
+
def name=(name)
|
77
|
+
@name = name.to_sym
|
78
|
+
Router.named_routes[@name] = self
|
79
|
+
@name
|
80
|
+
end
|
81
|
+
|
82
|
+
# === Compiled method ===
|
83
|
+
def generate(args = [], defaults = {})
|
84
|
+
raise GenerationError, "Cannot generate regexp Routes" if regexp?
|
85
|
+
|
86
|
+
params = extract_options_from_args!(args) || { }
|
87
|
+
|
88
|
+
# Support for anonymous params
|
89
|
+
unless args.empty?
|
90
|
+
# First, let's determine which variables are missing
|
91
|
+
variables = @variables - params.keys
|
92
|
+
|
93
|
+
raise GenerationError, "The route has #{@variables.length} variables: #{@variables.inspect}" if args.length > variables.length
|
94
|
+
|
95
|
+
args.each_with_index do |param, i|
|
96
|
+
params[variables[i]] ||= param
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
uri = @generator[params, defaults] or raise GenerationError, "Named route #{name} could not be generated with #{params.inspect}"
|
101
|
+
uri = Merb::Config[:path_prefix] + uri if Merb::Config[:path_prefix]
|
102
|
+
uri
|
141
103
|
end
|
142
104
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
105
|
+
def compiled_statement(first)
|
106
|
+
els_if = first ? ' if ' : ' elsif '
|
107
|
+
|
108
|
+
code = ""
|
109
|
+
code << els_if << condition_statements.join(" && ") << "\n"
|
110
|
+
|
111
|
+
# First, we need to always return the value of the
|
112
|
+
# deferred block if it explicitly matched the route
|
113
|
+
if @redirects && @deferred_procs.any?
|
114
|
+
code << " return [#{@index.inspect}, block_result] if request.matched?" << "\n"
|
115
|
+
code << " request.redirects!" << "\n"
|
116
|
+
code << " [#{@index.inspect}, { :url => #{@redirect_url.inspect}, :status => #{@redirect_status.inspect} }]" << "\n"
|
117
|
+
elsif @redirects
|
118
|
+
code << " request.redirects!" << "\n"
|
119
|
+
code << " [#{@index.inspect}, { :url => #{@redirect_url.inspect}, :status => #{@redirect_status.inspect} }]" << "\n"
|
120
|
+
elsif @deferred_procs.any?
|
121
|
+
code << " [#{@index.inspect}, block_result]" << "\n"
|
122
|
+
else
|
123
|
+
code << " [#{@index.inspect}, #{params_as_string}]" << "\n"
|
124
|
+
end
|
148
125
|
end
|
149
126
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
127
|
+
private
|
128
|
+
|
129
|
+
# === Compilation ===
|
130
|
+
|
131
|
+
def compile
|
132
|
+
compile_conditions
|
133
|
+
compile_params
|
134
|
+
@generator = Generator.new(@segments, @symbol_conditions, @identifiers).compiled
|
135
|
+
end
|
136
|
+
|
137
|
+
# The Generator class handles compiling the route down to a lambda that
|
138
|
+
# can generate the URL from a params hash and a default params hash.
|
139
|
+
class Generator #:nodoc:
|
140
|
+
|
141
|
+
def initialize(segments, symbol_conditions, identifiers)
|
142
|
+
@segments = segments
|
143
|
+
@symbol_conditions = symbol_conditions
|
144
|
+
@identifiers = identifiers
|
145
|
+
@stack = []
|
146
|
+
@opt_segment_count = 0
|
147
|
+
@opt_segment_stack = [[]]
|
148
|
+
end
|
149
|
+
|
150
|
+
def compiled
|
151
|
+
ruby = ""
|
152
|
+
ruby << "lambda do |params, defaults|\n"
|
153
|
+
ruby << " fragment = params.delete(:fragment)\n"
|
154
|
+
ruby << " query_params = params.dup\n"
|
155
|
+
|
156
|
+
with(@segments) do
|
157
|
+
ruby << " include_defaults = true\n"
|
158
|
+
ruby << " return unless url = #{block_for_level}\n"
|
159
|
+
end
|
160
|
+
|
161
|
+
ruby << " query_params.delete_if { |key, value| value.nil? }\n"
|
162
|
+
ruby << " unless query_params.empty?\n"
|
163
|
+
ruby << ' url << "?#{Merb::Request.params_to_query_string(query_params)}"' << "\n"
|
164
|
+
ruby << " end\n"
|
165
|
+
ruby << ' url << "##{fragment}" if fragment' << "\n"
|
166
|
+
ruby << " url\n"
|
167
|
+
ruby << "end\n"
|
168
|
+
|
169
|
+
eval(ruby)
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
# Cleans up methods a bunch. We don't need to pass the current segment
|
175
|
+
# level around everywhere anymore. It's kept track for us in the stack.
|
176
|
+
def with(segments, &block)
|
177
|
+
@stack.push(segments)
|
178
|
+
retval = yield
|
179
|
+
@stack.pop
|
180
|
+
retval
|
181
|
+
end
|
182
|
+
|
183
|
+
def segments
|
184
|
+
@stack.last || []
|
185
|
+
end
|
186
|
+
|
187
|
+
def symbol_segments
|
188
|
+
segments.flatten.select { |s| s.is_a?(Symbol) }
|
189
|
+
end
|
190
|
+
|
191
|
+
def current_segments
|
192
|
+
segments.select { |s| s.is_a?(Symbol) }
|
193
|
+
end
|
194
|
+
|
195
|
+
def nested_segments
|
196
|
+
segments.select { |s| s.is_a?(Array) }.flatten.select { |s| s.is_a?(Symbol) }
|
197
|
+
end
|
198
|
+
|
199
|
+
def block_for_level
|
200
|
+
ruby = ""
|
201
|
+
ruby << "if #{segment_level_matches_conditions}\n"
|
202
|
+
ruby << " #{remove_used_segments_in_query_path}\n"
|
203
|
+
ruby << " #{generate_optional_segments}\n"
|
204
|
+
ruby << %{ "#{combine_required_and_optional_segments}"\n}
|
205
|
+
ruby << "end"
|
206
|
+
end
|
207
|
+
|
208
|
+
def check_if_defaults_should_be_included
|
209
|
+
ruby = ""
|
210
|
+
ruby << "include_defaults = "
|
211
|
+
symbol_segments.each { |s| ruby << "params[#{s.inspect}] || " }
|
212
|
+
ruby << "false"
|
213
|
+
end
|
214
|
+
|
215
|
+
# --- Not so pretty ---
|
216
|
+
def segment_level_matches_conditions
|
217
|
+
conditions = current_segments.map do |segment|
|
218
|
+
condition = "(cached_#{segment} = params[#{segment.inspect}] || include_defaults && defaults[#{segment.inspect}])"
|
219
|
+
|
220
|
+
if @symbol_conditions[segment] && @symbol_conditions[segment].is_a?(Regexp)
|
221
|
+
condition << " =~ #{@symbol_conditions[segment].inspect}"
|
222
|
+
elsif @symbol_conditions[segment]
|
223
|
+
condition << " == #{@symbol_conditions[segment].inspect}"
|
224
|
+
end
|
225
|
+
|
226
|
+
condition
|
227
|
+
end
|
228
|
+
|
229
|
+
conditions << "true" if conditions.empty?
|
230
|
+
conditions.join(" && ")
|
231
|
+
end
|
232
|
+
|
233
|
+
def remove_used_segments_in_query_path
|
234
|
+
"#{current_segments.inspect}.each { |s| query_params.delete(s) }"
|
235
|
+
end
|
236
|
+
|
237
|
+
def generate_optional_segments
|
238
|
+
optionals = []
|
239
|
+
|
240
|
+
segments.each_with_index do |segment, i|
|
241
|
+
if segment.is_a?(Array) && segment.any? { |s| !s.is_a?(String) }
|
242
|
+
with(segment) do
|
243
|
+
@opt_segment_stack.last << (optional_name = "_optional_segments_#{@opt_segment_count += 1}")
|
244
|
+
@opt_segment_stack.push []
|
245
|
+
optionals << "#{check_if_defaults_should_be_included}\n"
|
246
|
+
optionals << "#{optional_name} = #{block_for_level}"
|
247
|
+
@opt_segment_stack.pop
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
optionals.join("\n")
|
253
|
+
end
|
254
|
+
|
255
|
+
def combine_required_and_optional_segments
|
256
|
+
bits = ""
|
257
|
+
|
258
|
+
segments.each_with_index do |segment, i|
|
259
|
+
bits << case
|
260
|
+
when segment.is_a?(String) then segment
|
261
|
+
when segment.is_a?(Symbol) then '#{param_for_route(cached_' + segment.to_s + ')}'
|
262
|
+
when segment.is_a?(Array) && segment.any? { |s| !s.is_a?(String) } then "\#{#{@opt_segment_stack.last.shift}}"
|
263
|
+
else ""
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
bits
|
268
|
+
end
|
269
|
+
|
270
|
+
def param_for_route(param)
|
271
|
+
case param
|
272
|
+
when String, Symbol, Numeric, TrueClass, FalseClass, NilClass
|
273
|
+
param
|
274
|
+
else
|
275
|
+
_, identifier = @identifiers.find { |klass, _| param.is_a?(klass) }
|
276
|
+
identifier ? param.send(identifier) : param
|
277
|
+
end
|
158
278
|
end
|
279
|
+
|
159
280
|
end
|
160
281
|
|
161
|
-
|
162
|
-
|
163
|
-
def
|
164
|
-
@
|
165
|
-
|
166
|
-
|
282
|
+
# === Conditions ===
|
283
|
+
|
284
|
+
def compile_conditions
|
285
|
+
@original_conditions = conditions.dup
|
286
|
+
|
287
|
+
if path = conditions[:path]
|
288
|
+
path = [path].flatten.compact
|
289
|
+
if path = compile_path(path)
|
290
|
+
conditions[:path] = Regexp.new("^#{path}$")
|
291
|
+
else
|
292
|
+
conditions.delete(:path)
|
293
|
+
end
|
294
|
+
end
|
167
295
|
end
|
168
296
|
|
169
|
-
#
|
170
|
-
#
|
171
|
-
|
172
|
-
|
297
|
+
# The path is passed in as an array of different parts. We basically have
|
298
|
+
# to concat all the parts together, then parse the path and extract the
|
299
|
+
# variables. However, if any of the parts are a regular expression, then
|
300
|
+
# we abort the parsing and just convert it to a regexp.
|
301
|
+
def compile_path(path)
|
302
|
+
@segments = []
|
303
|
+
compiled = ""
|
304
|
+
|
305
|
+
return nil if path.nil? || path.empty?
|
306
|
+
|
307
|
+
path.each do |part|
|
308
|
+
case part
|
309
|
+
when Regexp
|
310
|
+
@regexp = true
|
311
|
+
@segments = []
|
312
|
+
compiled << part.source.sub(/^\^/, '').sub(/\$$/, '')
|
313
|
+
when String
|
314
|
+
segments = segments_with_optionals_from_string(part.dup)
|
315
|
+
compile_path_segments(compiled, segments)
|
316
|
+
# Concat the segments
|
317
|
+
unless regexp?
|
318
|
+
if @segments[-1].is_a?(String) && segments[0].is_a?(String)
|
319
|
+
@segments[-1] << segments.shift
|
320
|
+
end
|
321
|
+
@segments.concat segments
|
322
|
+
end
|
323
|
+
else
|
324
|
+
raise ArgumentError.new("A route path can only be specified as a String or Regexp")
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
unless regexp?
|
329
|
+
@variables = @segments.flatten.select { |s| s.is_a?(Symbol) }
|
330
|
+
compiled.gsub!(%r[/+], '/')
|
331
|
+
compiled.gsub!(%r[(.+)/$], '\1')
|
332
|
+
end
|
333
|
+
|
334
|
+
compiled
|
173
335
|
end
|
174
336
|
|
175
|
-
#
|
176
|
-
|
177
|
-
#
|
178
|
-
# ==== Parameters
|
179
|
-
# path<String>:: The path to split into segments.
|
180
|
-
#
|
181
|
-
# ==== Returns
|
182
|
-
# Array:: The Symbol and String segments for the path.
|
183
|
-
def segments_from_path(path)
|
184
|
-
# Remove leading ^ and trailing $ from each segment (left-overs from regexp joining)
|
185
|
-
strip = proc { |str| str.gsub(/^\^/, '').gsub(/\$$/, '') }
|
337
|
+
# Simple nested parenthesis parser
|
338
|
+
def segments_with_optionals_from_string(path, nest_level = 0)
|
186
339
|
segments = []
|
340
|
+
|
341
|
+
# Extract all the segments at this parenthesis level
|
342
|
+
while segment = path.slice!(OPTIONAL_SEGMENT_REGEX)
|
343
|
+
# Append the segments that we came across so far
|
344
|
+
# at this level
|
345
|
+
segments.concat segments_from_string(segment[0..-2]) if segment.length > 1
|
346
|
+
# If the parenthesis that we came across is an opening
|
347
|
+
# then we need to jump to the higher level
|
348
|
+
if segment[-1,1] == '('
|
349
|
+
segments << segments_with_optionals_from_string(path, nest_level + 1)
|
350
|
+
else
|
351
|
+
# Throw an error if we can't actually go back down (aka syntax error)
|
352
|
+
raise "There are too many closing parentheses" if nest_level == 0
|
353
|
+
return segments
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Save any last bit of the string that didn't match the original regex
|
358
|
+
segments.concat segments_from_string(path) unless path.empty?
|
359
|
+
|
360
|
+
# Throw an error if the string should not actually be done (aka syntax error)
|
361
|
+
raise "You have too many opening parentheses" unless nest_level == 0
|
362
|
+
|
363
|
+
segments
|
364
|
+
end
|
365
|
+
|
366
|
+
def segments_from_string(path)
|
367
|
+
segments = []
|
368
|
+
|
187
369
|
while match = (path.match(SEGMENT_REGEXP))
|
188
|
-
segments <<
|
370
|
+
segments << match.pre_match unless match.pre_match.empty?
|
189
371
|
segments << match[2].intern
|
190
|
-
path =
|
372
|
+
path = match.post_match
|
191
373
|
end
|
192
|
-
|
374
|
+
|
375
|
+
segments << path unless path.empty?
|
193
376
|
segments
|
194
377
|
end
|
195
378
|
|
196
|
-
#
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
379
|
+
# --- Yeah, this could probably be refactored
|
380
|
+
def compile_path_segments(compiled, segments)
|
381
|
+
segments.each do |segment|
|
382
|
+
case segment
|
383
|
+
when String
|
384
|
+
compiled << Regexp.escape(segment)
|
385
|
+
when Symbol
|
386
|
+
condition = (@symbol_conditions[segment] ||= @conditions.delete(segment))
|
387
|
+
compiled << compile_segment_condition(condition)
|
388
|
+
# Create a param for the Symbol segment if none already exists
|
389
|
+
@params[segment] = "#{segment.inspect}" unless @params.has_key?(segment)
|
390
|
+
@placeholders[segment] ||= capturing_parentheses_count(compiled)
|
391
|
+
when Array
|
392
|
+
compiled << "(?:"
|
393
|
+
compile_path_segments(compiled, segment)
|
394
|
+
compiled << ")?"
|
395
|
+
else
|
396
|
+
raise ArgumentError, "conditions[:path] segments can only be a Strings, Symbols, or Arrays"
|
397
|
+
end
|
398
|
+
end
|
206
399
|
end
|
207
400
|
|
208
|
-
#
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
401
|
+
# Handles anchors in Regexp conditions
|
402
|
+
def compile_segment_condition(condition)
|
403
|
+
return "(#{SEGMENT_CHARACTERS}+)" unless condition
|
404
|
+
return "(#{condition})" unless condition.is_a?(Regexp)
|
405
|
+
|
406
|
+
condition = condition.source
|
407
|
+
# Handle the start anchor
|
408
|
+
condition = if condition =~ /^\^/
|
409
|
+
condition[1..-1]
|
410
|
+
else
|
411
|
+
"#{SEGMENT_CHARACTERS}*#{condition}"
|
412
|
+
end
|
413
|
+
# Handle the end anchor
|
414
|
+
condition = if condition =~ /\$$/
|
415
|
+
condition[0..-2]
|
416
|
+
else
|
417
|
+
"#{condition}#{SEGMENT_CHARACTERS}*"
|
418
|
+
end
|
419
|
+
|
420
|
+
"(#{condition})"
|
421
|
+
end
|
422
|
+
|
423
|
+
def compile_params
|
424
|
+
# Loop through each param and compile it
|
425
|
+
@defaults.merge(@params).each do |key, value|
|
426
|
+
if value.nil?
|
427
|
+
@params.delete(key)
|
428
|
+
elsif value.is_a?(String)
|
429
|
+
@params[key] = compile_param(value)
|
430
|
+
else
|
431
|
+
@params[key] = value.inspect
|
432
|
+
end
|
433
|
+
end
|
214
434
|
end
|
215
435
|
|
216
|
-
#
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
if params.is_a? Hash
|
234
|
-
if segment.to_s =~ /_id/ && params[:id].respond_to?(segment)
|
235
|
-
params[segment] = params[:id].send(segment)
|
236
|
-
end
|
237
|
-
query_params.delete segment
|
238
|
-
params[segment] || fallback[segment]
|
436
|
+
# This was pretty much a copy / paste from the old router
|
437
|
+
def compile_param(value)
|
438
|
+
result = []
|
439
|
+
match = true
|
440
|
+
while match
|
441
|
+
if match = SEGMENT_REGEXP_WITH_BRACKETS.match(value)
|
442
|
+
result << match.pre_match.inspect unless match.pre_match.empty?
|
443
|
+
placeholder_key = match[1][1..-1].intern
|
444
|
+
if match[2] # has brackets, e.g. :path[2]
|
445
|
+
result << "#{placeholder_key}#{match[3]}"
|
446
|
+
else # no brackets, e.g. a named placeholder such as :controller
|
447
|
+
if place = @placeholders[placeholder_key]
|
448
|
+
# result << "(path#{place} || )" # <- Defaults
|
449
|
+
with_defaults = ["(path#{place}"]
|
450
|
+
with_defaults << " || #{@defaults[placeholder_key].inspect}" if @defaults[placeholder_key]
|
451
|
+
with_defaults << ")"
|
452
|
+
result << with_defaults.join
|
239
453
|
else
|
240
|
-
|
241
|
-
params.to_param
|
242
|
-
elsif segment == :id && params.is_a?(Fixnum)
|
243
|
-
params
|
244
|
-
elsif params.respond_to?(segment)
|
245
|
-
params.send(segment)
|
246
|
-
else
|
247
|
-
fallback[segment]
|
248
|
-
end
|
454
|
+
raise GenerationError, "Placeholder not found while compiling routes: #{placeholder_key.inspect}. Add it to the conditions part of the route."
|
249
455
|
end
|
250
|
-
elsif segment.respond_to? :to_s
|
251
|
-
segment
|
252
|
-
else
|
253
|
-
raise "Segment type '#{segment.class}' can't be converted to a string"
|
254
456
|
end
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
457
|
+
value = match.post_match
|
458
|
+
elsif match = JUST_BRACKETS.match(value)
|
459
|
+
result << match.pre_match.inspect unless match.pre_match.empty?
|
460
|
+
result << "path#{match[1]}"
|
461
|
+
value = match.post_match
|
462
|
+
else
|
463
|
+
result << value.inspect unless value.empty?
|
464
|
+
end
|
260
465
|
end
|
261
|
-
|
262
|
-
|
466
|
+
|
467
|
+
result.join(' + ').gsub("\\_", "_")
|
468
|
+
end
|
469
|
+
|
470
|
+
def condition_statements
|
471
|
+
statements = []
|
472
|
+
|
473
|
+
# First, let's build the conditions for the regular
|
474
|
+
conditions.each_pair do |key, value|
|
475
|
+
statements << case value
|
476
|
+
when Regexp
|
477
|
+
captures = ""
|
478
|
+
|
479
|
+
if (max = capturing_parentheses_count(value)) > 0
|
480
|
+
captures << (1..max).to_a.map { |n| "#{key}#{n}" }.join(", ")
|
481
|
+
captures << " = "
|
482
|
+
captures << (1..max).to_a.map { |n| "$#{n}" }.join(", ")
|
483
|
+
end
|
484
|
+
|
485
|
+
# Note: =~ is slightly faster than .match
|
486
|
+
%{(#{value.inspect} =~ cached_#{key} #{' && ((' + captures + ') || true)' unless captures.empty?})}
|
487
|
+
when Array
|
488
|
+
%{(#{arrays_to_regexps(value).inspect} =~ cached_#{key})}
|
489
|
+
else
|
490
|
+
%{(cached_#{key} == #{value.inspect})}
|
491
|
+
end
|
263
492
|
end
|
264
|
-
|
265
|
-
|
493
|
+
|
494
|
+
# The first one is special, so let's extract it
|
495
|
+
if first = @deferred_procs.first
|
496
|
+
deferred = ""
|
497
|
+
deferred << "(block_result = "
|
498
|
+
deferred << "request._process_block_return("
|
499
|
+
deferred << "#{first}.call(request, #{params_as_string})"
|
500
|
+
deferred << ")"
|
501
|
+
deferred << ")"
|
502
|
+
|
503
|
+
# Let's build the rest of them now
|
504
|
+
if @deferred_procs.length > 1
|
505
|
+
deferred << deferred_condition_statement(@deferred_procs[1..-1])
|
506
|
+
end
|
507
|
+
|
508
|
+
statements << deferred
|
266
509
|
end
|
267
|
-
|
268
|
-
|
510
|
+
|
511
|
+
statements
|
512
|
+
end
|
513
|
+
|
514
|
+
# (request.matched? || ((block_result = process(proc.call))))
|
515
|
+
def deferred_condition_statement(deferred)
|
516
|
+
if current = deferred.first
|
517
|
+
html = " && (request.matched? || ("
|
518
|
+
html << "(block_result = "
|
519
|
+
html << "request._process_block_return("
|
520
|
+
html << "#{current}.call(request, block_result)"
|
521
|
+
html << ")"
|
522
|
+
html << ")"
|
523
|
+
html << "#{deferred_condition_statement(deferred[1..-1])}"
|
524
|
+
html << "))"
|
269
525
|
end
|
270
|
-
url
|
271
526
|
end
|
272
527
|
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
# ==== Params
|
277
|
-
# params_as_string<String>::
|
278
|
-
# The params hash as a string, e.g. ":foo => 'bar'".
|
279
|
-
#
|
280
|
-
# ==== Returns
|
281
|
-
# Array:: All the conditions as eval'able strings.
|
282
|
-
def if_conditions(params_as_string)
|
283
|
-
cond = []
|
284
|
-
condition_string = proc do |key, value, regexp_string|
|
285
|
-
max = Behavior.count_parens_up_to(value.source, value.source.size)
|
286
|
-
captures = max == 0 ? "" : (1..max).to_a.map{ |n| "#{key}#{n}" }.join(", ") + " = " +
|
287
|
-
(1..max).to_a.map{ |n| "$#{n}"}.join(", ")
|
288
|
-
" (#{value.inspect} =~ #{regexp_string}) #{" && (" + captures + ")" unless captures.empty?}"
|
289
|
-
end
|
290
|
-
@conditions.each_pair do |key, value|
|
291
|
-
|
292
|
-
# Note: =~ is slightly faster than .match
|
293
|
-
cond << case key
|
294
|
-
when :path then condition_string[key, value, "cached_path"]
|
295
|
-
when :method then condition_string[key, value, "cached_method"]
|
296
|
-
else condition_string[key, value, "request.#{key}.to_s"]
|
297
|
-
end
|
528
|
+
def params_as_string
|
529
|
+
elements = params.keys.map do |k|
|
530
|
+
"#{k.inspect} => #{params[k]}"
|
298
531
|
end
|
299
|
-
|
300
|
-
str = " # #{@conditional_block.inspect.scan(/@([^>]+)/).flatten.first}\n"
|
301
|
-
str << " (block_result = #{CachedProc.new(@conditional_block)}.call(request, params.merge({#{params_as_string}})))" if @conditional_block
|
302
|
-
cond << str
|
303
|
-
end
|
304
|
-
cond
|
532
|
+
"{#{elements.join(', ')}}"
|
305
533
|
end
|
306
534
|
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
# String:: The code corresponding to the route in a form suited for eval.
|
316
|
-
def compile(first = false)
|
317
|
-
code = ""
|
318
|
-
default_params = { :action => "index" }
|
319
|
-
get_value = proc do |key|
|
320
|
-
if default_params.has_key?(key) && params[key][0] != ?"
|
321
|
-
"#{params[key]} || \"#{default_params[key]}\""
|
535
|
+
# ---------- Utilities ----------
|
536
|
+
|
537
|
+
def arrays_to_regexps(condition)
|
538
|
+
return condition unless condition.is_a?(Array)
|
539
|
+
|
540
|
+
source = condition.map do |value|
|
541
|
+
value = if value.is_a?(Regexp)
|
542
|
+
value.source
|
322
543
|
else
|
323
|
-
"
|
544
|
+
"^#{Regexp.escape(value.to_s)}$"
|
324
545
|
end
|
546
|
+
"(?:#{value})"
|
325
547
|
end
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
548
|
+
|
549
|
+
Regexp.compile(source.join('|'))
|
550
|
+
end
|
551
|
+
|
552
|
+
def segment_level_to_s(segments)
|
553
|
+
(segments || []).inject('') do |str, seg|
|
554
|
+
str << case seg
|
555
|
+
when String then seg
|
556
|
+
when Symbol then ":#{seg}"
|
557
|
+
when Array then "(#{segment_level_to_s(seg)})"
|
558
|
+
end
|
335
559
|
end
|
336
560
|
end
|
337
561
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
puts @behavior.send(:ancestors).reverse.map{|a| a.inspect}.join("\n"); puts @behavior.inspect; puts
|
342
|
-
else
|
343
|
-
puts "No behavior to trace #{self}"
|
344
|
-
end
|
562
|
+
def capturing_parentheses_count(regexp)
|
563
|
+
regexp = regexp.source if regexp.is_a?(Regexp)
|
564
|
+
regexp.scan(/(?!\\)[(](?!\?[#=:!>-imx])/).length
|
345
565
|
end
|
346
|
-
end
|
347
|
-
end
|
566
|
+
end
|
567
|
+
end
|
348
568
|
end
|