merb-core 0.9.2
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 +20 -0
- data/README +21 -0
- data/Rakefile +285 -0
- data/TODO +0 -0
- data/bin/merb +8 -0
- data/bin/merb-specs +5 -0
- data/docs/bootloading.dox +57 -0
- data/docs/documentation_standards +40 -0
- data/docs/new_render_api +51 -0
- data/lib/merb-core.rb +304 -0
- data/lib/merb-core/autoload.rb +29 -0
- data/lib/merb-core/bootloader.rb +601 -0
- data/lib/merb-core/config.rb +284 -0
- data/lib/merb-core/constants.rb +43 -0
- data/lib/merb-core/controller/abstract_controller.rb +531 -0
- data/lib/merb-core/controller/exceptions.rb +257 -0
- data/lib/merb-core/controller/merb_controller.rb +214 -0
- data/lib/merb-core/controller/mime.rb +88 -0
- data/lib/merb-core/controller/mixins/controller.rb +262 -0
- data/lib/merb-core/controller/mixins/render.rb +324 -0
- data/lib/merb-core/controller/mixins/responder.rb +464 -0
- data/lib/merb-core/controller/template.rb +205 -0
- data/lib/merb-core/core_ext.rb +12 -0
- data/lib/merb-core/core_ext/class.rb +192 -0
- data/lib/merb-core/core_ext/hash.rb +422 -0
- data/lib/merb-core/core_ext/kernel.rb +304 -0
- data/lib/merb-core/core_ext/mash.rb +154 -0
- data/lib/merb-core/core_ext/object.rb +136 -0
- data/lib/merb-core/core_ext/object_space.rb +14 -0
- data/lib/merb-core/core_ext/rubygems.rb +28 -0
- data/lib/merb-core/core_ext/set.rb +41 -0
- data/lib/merb-core/core_ext/string.rb +69 -0
- data/lib/merb-core/dispatch/cookies.rb +92 -0
- data/lib/merb-core/dispatch/dispatcher.rb +233 -0
- data/lib/merb-core/dispatch/exceptions.html.erb +297 -0
- data/lib/merb-core/dispatch/request.rb +560 -0
- data/lib/merb-core/dispatch/router.rb +141 -0
- data/lib/merb-core/dispatch/router/behavior.rb +777 -0
- data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
- data/lib/merb-core/dispatch/router/route.rb +212 -0
- data/lib/merb-core/dispatch/session.rb +28 -0
- data/lib/merb-core/dispatch/session/cookie.rb +166 -0
- data/lib/merb-core/dispatch/session/memcached.rb +161 -0
- data/lib/merb-core/dispatch/session/memory.rb +234 -0
- data/lib/merb-core/gem_ext/erubis.rb +19 -0
- data/lib/merb-core/logger.rb +230 -0
- data/lib/merb-core/plugins.rb +25 -0
- data/lib/merb-core/rack.rb +15 -0
- data/lib/merb-core/rack/adapter.rb +42 -0
- data/lib/merb-core/rack/adapter/ebb.rb +22 -0
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +24 -0
- data/lib/merb-core/rack/adapter/fcgi.rb +16 -0
- data/lib/merb-core/rack/adapter/irb.rb +108 -0
- data/lib/merb-core/rack/adapter/mongrel.rb +25 -0
- data/lib/merb-core/rack/adapter/runner.rb +27 -0
- data/lib/merb-core/rack/adapter/thin.rb +27 -0
- data/lib/merb-core/rack/adapter/webrick.rb +35 -0
- data/lib/merb-core/rack/application.rb +77 -0
- data/lib/merb-core/rack/handler/mongrel.rb +97 -0
- data/lib/merb-core/server.rb +184 -0
- data/lib/merb-core/test.rb +10 -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 +257 -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 +269 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +136 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +293 -0
- data/lib/merb-core/test/run_specs.rb +38 -0
- data/lib/merb-core/test/tasks/spectasks.rb +39 -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/vendor/facets.rb +2 -0
- data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
- data/lib/merb-core/vendor/facets/inflect.rb +211 -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 +139 -0
- data/spec/private/config/environment_spec.rb +13 -0
- data/spec/private/config/spec_helper.rb +1 -0
- data/spec/private/core_ext/hash_spec.rb +506 -0
- data/spec/private/core_ext/kernel_spec.rb +46 -0
- data/spec/private/core_ext/object_spec.rb +39 -0
- data/spec/private/core_ext/set_spec.rb +26 -0
- data/spec/private/core_ext/string_spec.rb +9 -0
- data/spec/private/dispatch/cookies_spec.rb +107 -0
- data/spec/private/dispatch/dispatch_spec.rb +26 -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/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 +1 -0
- data/spec/private/dispatch/fixture/config/router.rb +35 -0
- data/spec/private/dispatch/fixture/log/development.log +1 -0
- data/spec/private/dispatch/fixture/log/merb.4000.pid +1 -0
- data/spec/private/dispatch/fixture/log/merb_test.log +2040 -0
- data/spec/private/dispatch/fixture/log/production.log +1 -0
- data/spec/private/dispatch/fixture/merb.4000.pid +1 -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/spec_helper.rb +1 -0
- data/spec/private/plugins/plugin_spec.rb +81 -0
- data/spec/private/rack/application_spec.rb +43 -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/display.rb +54 -0
- data/spec/public/abstract_controller/controllers/filters.rb +167 -0
- data/spec/public/abstract_controller/controllers/helpers.rb +31 -0
- data/spec/public/abstract_controller/controllers/partial.rb +106 -0
- data/spec/public/abstract_controller/controllers/render.rb +86 -0
- data/spec/public/abstract_controller/controllers/views/helpers/capture/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/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_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_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 +80 -0
- data/spec/public/abstract_controller/helper_spec.rb +13 -0
- data/spec/public/abstract_controller/partial_spec.rb +53 -0
- data/spec/public/abstract_controller/render_spec.rb +70 -0
- data/spec/public/abstract_controller/spec_helper.rb +27 -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/base_spec.rb +31 -0
- data/spec/public/controller/controllers/base.rb +41 -0
- data/spec/public/controller/controllers/display.rb +40 -0
- data/spec/public/controller/controllers/responder.rb +67 -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/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/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 +34 -0
- data/spec/public/controller/log/merb.4000.pid +1 -0
- data/spec/public/controller/responder_spec.rb +95 -0
- data/spec/public/controller/spec_helper.rb +9 -0
- data/spec/public/controller/url_spec.rb +152 -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.4000.pid +1 -0
- data/spec/public/directory_structure/directory/log/merb_test.log +265 -0
- data/spec/public/directory_structure/directory/merb.4000.pid +1 -0
- data/spec/public/directory_structure/directory_spec.rb +44 -0
- data/spec/public/logger/logger_spec.rb +175 -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.4000.pid +1 -0
- data/spec/public/reloading/directory/log/merb_test.log +59 -0
- data/spec/public/reloading/directory/merb.4000.pid +1 -0
- data/spec/public/reloading/reload_spec.rb +80 -0
- data/spec/public/request/multipart_spec.rb +15 -0
- data/spec/public/request/request_spec.rb +207 -0
- data/spec/public/router/default_spec.rb +21 -0
- data/spec/public/router/deferred_spec.rb +22 -0
- data/spec/public/router/namespace_spec.rb +113 -0
- data/spec/public/router/nested_resources_spec.rb +34 -0
- data/spec/public/router/resource_spec.rb +45 -0
- data/spec/public/router/resources_spec.rb +57 -0
- data/spec/public/router/spec_helper.rb +72 -0
- data/spec/public/router/special_spec.rb +44 -0
- data/spec/public/router/string_spec.rb +61 -0
- data/spec/public/template/template_spec.rb +92 -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 +378 -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 +30 -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 +153 -0
- data/spec/public/test/route_helper_spec.rb +54 -0
- data/spec/public/test/route_matchers_spec.rb +133 -0
- data/spec/public/test/view_helper_spec.rb +96 -0
- data/spec/public/test/view_matchers_spec.rb +107 -0
- data/spec/spec_helper.rb +71 -0
- metadata +488 -0
|
@@ -0,0 +1,141 @@
|
|
|
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
|
+
class Router
|
|
7
|
+
SEGMENT_REGEXP = /(:([a-z_][a-z0-9_]*|:))/
|
|
8
|
+
SEGMENT_REGEXP_WITH_BRACKETS = /(:[a-z_]+)(\[(\d+)\])?/
|
|
9
|
+
JUST_BRACKETS = /\[(\d+)\]/
|
|
10
|
+
PARENTHETICAL_SEGMENT_STRING = "([^\/.,;?]+)".freeze
|
|
11
|
+
|
|
12
|
+
@@named_routes = {}
|
|
13
|
+
@@routes = []
|
|
14
|
+
cattr_accessor :routes, :named_routes
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
|
|
18
|
+
# Appends the generated routes to the current routes.
|
|
19
|
+
#
|
|
20
|
+
# ==== Parameters
|
|
21
|
+
# &block::
|
|
22
|
+
# A block that generates new routes when yielded a new Behavior.
|
|
23
|
+
def append(&block)
|
|
24
|
+
prepare(@@routes, [], &block)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Prepends the generated routes to the current routes.
|
|
28
|
+
#
|
|
29
|
+
# ==== Parameters
|
|
30
|
+
# &block::
|
|
31
|
+
# A block that generates new routes when yielded a new Behavior.
|
|
32
|
+
def prepend(&block)
|
|
33
|
+
prepare([], @@routes, &block)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Prepares new routes and adds them to existing routes.
|
|
37
|
+
#
|
|
38
|
+
# ==== Parameters
|
|
39
|
+
# first<Array>:: An array of routes to add before the generated routes.
|
|
40
|
+
# last<Array>:: An array of routes to add after the generated routes.
|
|
41
|
+
# &block:: A block that generates new routes.
|
|
42
|
+
#
|
|
43
|
+
# ==== Block parameters (&block)
|
|
44
|
+
# new_behavior<Behavior>:: Behavior for child routes.
|
|
45
|
+
def prepare(first = [], last = [], &block)
|
|
46
|
+
@@routes = []
|
|
47
|
+
yield Behavior.new({}, { :action => 'index' }) # defaults
|
|
48
|
+
@@routes = first + @@routes + last
|
|
49
|
+
compile
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# ==== Returns
|
|
53
|
+
# String:: A routing lambda statement generated from the routes.
|
|
54
|
+
def compiled_statement
|
|
55
|
+
@@compiled_statement = "def match(request)\n"
|
|
56
|
+
@@compiled_statement << " params = request.params\n"
|
|
57
|
+
@@compiled_statement << " cached_path = request.path\n cached_method = request.method.to_s\n "
|
|
58
|
+
@@routes.each_with_index { |route, i| @@compiled_statement << route.compile(i == 0) }
|
|
59
|
+
@@compiled_statement << " else\n [nil, {}]\n"
|
|
60
|
+
@@compiled_statement << " end\n"
|
|
61
|
+
@@compiled_statement << "end"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Defines the match function for this class based on the
|
|
65
|
+
# compiled_statement.
|
|
66
|
+
def compile
|
|
67
|
+
puts "compiled route: #{compiled_statement}" if $DEBUG
|
|
68
|
+
eval(compiled_statement, binding, __FILE__, __LINE__)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Generates a URL based on passed options.
|
|
72
|
+
#
|
|
73
|
+
# ==== Parameters
|
|
74
|
+
# name<~to_sym, Hash>:: The name of the route to generate.
|
|
75
|
+
# params<Hash>:: The params to use in the route generation.
|
|
76
|
+
# fallback<Hash>:: Parameters for generating a fallback URL.
|
|
77
|
+
#
|
|
78
|
+
# ==== Returns
|
|
79
|
+
# String:: The generated URL.
|
|
80
|
+
#
|
|
81
|
+
# ==== Alternatives
|
|
82
|
+
# If name is a hash, it will be merged with params and passed on to
|
|
83
|
+
# generate_for_default_route along with fallback.
|
|
84
|
+
def generate(name, params = {}, fallback = {})
|
|
85
|
+
if name.is_a? Hash
|
|
86
|
+
return generate_for_default_route(name.merge(params), fallback)
|
|
87
|
+
end
|
|
88
|
+
name = name.to_sym
|
|
89
|
+
unless @@named_routes.key? name
|
|
90
|
+
raise "Named route not found: #{name}"
|
|
91
|
+
else
|
|
92
|
+
@@named_routes[name].generate(params, fallback)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Generates a URL based on the default route scheme of
|
|
97
|
+
# "/:controller/:action/:id.:format".
|
|
98
|
+
#
|
|
99
|
+
# ==== Parameters
|
|
100
|
+
# params<Hash>::
|
|
101
|
+
# The primary parameters to create the route from (see below).
|
|
102
|
+
# fallback<Hash>:: Fallback parameters. Same options as params.
|
|
103
|
+
#
|
|
104
|
+
# ==== Options (params)
|
|
105
|
+
# :controller<~to_s>:: The controller name. Required.
|
|
106
|
+
# :action<~to_s>:: The action name. Required.
|
|
107
|
+
# :id<~to_s>:: The ID for use in the action.
|
|
108
|
+
# :format<~to_s>:: The format of the preferred response.
|
|
109
|
+
#
|
|
110
|
+
# ==== Returns
|
|
111
|
+
# String:: The generated URL.
|
|
112
|
+
def generate_for_default_route(params, fallback)
|
|
113
|
+
query_params = params.reject do |k,v|
|
|
114
|
+
[:controller, :action, :id, :format].include?(k.to_sym)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
controller = params[:controller] || fallback[:controller]
|
|
118
|
+
raise "Controller Not Specified" unless controller
|
|
119
|
+
url = "/#{controller}"
|
|
120
|
+
|
|
121
|
+
if params[:action] || params[:id] || params[:format] || !query_params.empty?
|
|
122
|
+
action = params[:action] || fallback[:action]
|
|
123
|
+
raise "Action Not Specified" unless action
|
|
124
|
+
url += "/#{action}"
|
|
125
|
+
end
|
|
126
|
+
if params[:id]
|
|
127
|
+
url += "/#{params[:id]}"
|
|
128
|
+
end
|
|
129
|
+
if format = params[:format]
|
|
130
|
+
format = fallback[:format] if format == :current
|
|
131
|
+
url += ".#{format}"
|
|
132
|
+
end
|
|
133
|
+
unless query_params.empty?
|
|
134
|
+
url += "?" + Merb::Request.params_to_query_string(query_params)
|
|
135
|
+
end
|
|
136
|
+
url
|
|
137
|
+
end
|
|
138
|
+
end # self
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,777 @@
|
|
|
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
|
|
11
|
+
attr_accessor :parent
|
|
12
|
+
@@parent_resource = []
|
|
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
|
+
# This optional hash helps refine the settings for the route.
|
|
112
|
+
# When combined with a block it can help keep your routes DRY
|
|
113
|
+
# &block::
|
|
114
|
+
# Passes a new instance of a Behavior object into the optional block so
|
|
115
|
+
# that sub-matching and routes nesting may occur.
|
|
116
|
+
#
|
|
117
|
+
# ==== Returns
|
|
118
|
+
# Behavior::
|
|
119
|
+
# A new instance of Behavior with the specified path and conditions.
|
|
120
|
+
#
|
|
121
|
+
# +Tip+: When nesting always make sure the most inner sub-match registers
|
|
122
|
+
# a Route and doesn't just returns new Behaviors.
|
|
123
|
+
#
|
|
124
|
+
# ==== Examples
|
|
125
|
+
#
|
|
126
|
+
# # registers /foo/bar to controller => "foo", :action => "bar"
|
|
127
|
+
# # and /foo/baz to controller => "foo", :action => "caz"
|
|
128
|
+
# r.match "/foo", :controller => "foo" do |f|
|
|
129
|
+
# f.match("/bar").to(:action => "bar")
|
|
130
|
+
# f.match("/baz").to(:action => "caz")
|
|
131
|
+
# end
|
|
132
|
+
#
|
|
133
|
+
# r.match "/foo", :controller => "foo" do |f|
|
|
134
|
+
# f.match("/bar", :action => "bar")
|
|
135
|
+
# f.match("/baz", :action => "caz")
|
|
136
|
+
# end # => doesn't register any routes at all
|
|
137
|
+
#
|
|
138
|
+
# # match also takes regular expressions
|
|
139
|
+
# r.match(%r[/account/([a-z]{4,6})]).to(:controller => "account",
|
|
140
|
+
# :action => "show", :id => "[1]")
|
|
141
|
+
#---
|
|
142
|
+
# @public
|
|
143
|
+
def match(path = '', conditions = {}, &block)
|
|
144
|
+
if path.is_a? Hash
|
|
145
|
+
conditions = path
|
|
146
|
+
else
|
|
147
|
+
conditions[:path] = path
|
|
148
|
+
end
|
|
149
|
+
match_without_path(conditions, &block)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Generates a new child behavior without the path if the path matches
|
|
153
|
+
# an empty string. Yields the new behavior to a block.
|
|
154
|
+
#
|
|
155
|
+
# ==== Parameters
|
|
156
|
+
# conditions<Hash>:: Optional conditions to pass to the new route.
|
|
157
|
+
#
|
|
158
|
+
# ==== Block parameters
|
|
159
|
+
# new_behavior<Behavior>:: The child behavior.
|
|
160
|
+
#
|
|
161
|
+
# ==== Returns
|
|
162
|
+
# Behavior:: The new behavior.
|
|
163
|
+
def match_without_path(conditions = {})
|
|
164
|
+
new_behavior = self.class.new(conditions, {}, self)
|
|
165
|
+
conditions.delete :path if ['', '^$'].include?(conditions[:path])
|
|
166
|
+
yield new_behavior if block_given?
|
|
167
|
+
new_behavior
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# ==== Parameters
|
|
171
|
+
# params<Hash>:: Optional additional parameters for generating the route.
|
|
172
|
+
# &conditional_block:: A conditional block to be passed to Route.new.
|
|
173
|
+
#
|
|
174
|
+
# ==== Returns
|
|
175
|
+
# Route:: A new route based on this behavior.
|
|
176
|
+
def to_route(params = {}, &conditional_block)
|
|
177
|
+
@params.merge! params
|
|
178
|
+
Route.new compiled_conditions, compiled_params, self, &conditional_block
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Creates a Route from one or more Behavior objects, unless a +block+ is
|
|
182
|
+
# passed in.
|
|
183
|
+
#
|
|
184
|
+
# ==== Parameters
|
|
185
|
+
# params<Hash>:: The parameters the route maps to.
|
|
186
|
+
# &block::
|
|
187
|
+
# Optional block. A new Behavior object is yielded and further #to
|
|
188
|
+
# operations may be called in the block.
|
|
189
|
+
#
|
|
190
|
+
# ==== Block parameters
|
|
191
|
+
# new_behavior<Behavior>:: The child behavior.
|
|
192
|
+
#
|
|
193
|
+
# ==== Returns
|
|
194
|
+
# Route:: It registers a new route and returns it.
|
|
195
|
+
#
|
|
196
|
+
# ==== Examples
|
|
197
|
+
# r.match('/:controller/:id).to(:action => 'show')
|
|
198
|
+
#
|
|
199
|
+
# r.to :controller => 'simple' do |s|
|
|
200
|
+
# s.match('/test').to(:action => 'index')
|
|
201
|
+
# s.match('/other').to(:action => 'other')
|
|
202
|
+
# end
|
|
203
|
+
#---
|
|
204
|
+
# @public
|
|
205
|
+
def to(params = {}, &block)
|
|
206
|
+
if block_given?
|
|
207
|
+
new_behavior = self.class.new({}, params, self)
|
|
208
|
+
yield new_behavior if block_given?
|
|
209
|
+
new_behavior
|
|
210
|
+
else
|
|
211
|
+
to_route(params).register
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Takes a block and stores it for deferred conditional routes. The block
|
|
216
|
+
# takes the +request+ object and the +params+ hash as parameters.
|
|
217
|
+
#
|
|
218
|
+
# ==== Parameters
|
|
219
|
+
# params<Hash>:: Parameters and conditions associated with this behavior.
|
|
220
|
+
# &conditional_block::
|
|
221
|
+
# A block with the conditions to be met for the behavior to take
|
|
222
|
+
# effect.
|
|
223
|
+
#
|
|
224
|
+
# ==== Returns
|
|
225
|
+
# Route :: The default route.
|
|
226
|
+
#
|
|
227
|
+
# ==== Examples
|
|
228
|
+
# r.defer_to do |request, params|
|
|
229
|
+
# params.merge :controller => 'here',
|
|
230
|
+
# :action => 'there' if request.xhr?
|
|
231
|
+
# end
|
|
232
|
+
#---
|
|
233
|
+
# @public
|
|
234
|
+
def defer_to(params = {}, &conditional_block)
|
|
235
|
+
Router.routes << (route = to_route(params, &conditional_block))
|
|
236
|
+
route
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Creates the most common routes /:controller/:action/:id.format when
|
|
240
|
+
# called with no arguments.
|
|
241
|
+
# You can pass a hash or a block to add parameters or override the default
|
|
242
|
+
# behavior.
|
|
243
|
+
#
|
|
244
|
+
# ==== Parameters
|
|
245
|
+
# params<Hash>::
|
|
246
|
+
# This optional hash can be used to augment the default settings
|
|
247
|
+
# &block::
|
|
248
|
+
# When passing a block a new behavior is yielded and more refinement is
|
|
249
|
+
# possible.
|
|
250
|
+
#
|
|
251
|
+
# ==== Returns
|
|
252
|
+
# Route:: the default route
|
|
253
|
+
#
|
|
254
|
+
# ==== Examples
|
|
255
|
+
#
|
|
256
|
+
# # Passing an extra parameter "mode" to all matches
|
|
257
|
+
# r.default_routes :mode => "default"
|
|
258
|
+
#
|
|
259
|
+
# # specifying exceptions within a block
|
|
260
|
+
# r.default_routes do |nr|
|
|
261
|
+
# nr.defer_to do |request, params|
|
|
262
|
+
# nr.match(:protocol => "http://").to(:controller => "login",
|
|
263
|
+
# :action => "new") if request.env["REQUEST_URI"] =~ /\/private\//
|
|
264
|
+
# end
|
|
265
|
+
# end
|
|
266
|
+
#---
|
|
267
|
+
# @public
|
|
268
|
+
def default_routes(params = {}, &block)
|
|
269
|
+
match(%r{/:controller(/:action(/:id)?)?(\.:format)?}).to(params, &block)
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Creates a namespace for a route. This way you can have logical
|
|
273
|
+
# separation to your routes.
|
|
274
|
+
#
|
|
275
|
+
# ==== Parameters
|
|
276
|
+
# name_or_path<String, Symbol>:: The name or path of the namespace.
|
|
277
|
+
# options<Hash>:: Optional hash, set :path if you want to override what appears on the url
|
|
278
|
+
# &block::
|
|
279
|
+
# A new Behavior instance is yielded in the block for nested resources.
|
|
280
|
+
#
|
|
281
|
+
# ==== Block parameters
|
|
282
|
+
# r<Behavior>:: The namespace behavior object.
|
|
283
|
+
#
|
|
284
|
+
# ==== Examples
|
|
285
|
+
# r.namespace :admin do |admin|
|
|
286
|
+
# admin.resources :accounts
|
|
287
|
+
# admin.resource :email
|
|
288
|
+
# end
|
|
289
|
+
#
|
|
290
|
+
# # /super_admin/accounts
|
|
291
|
+
# r.namespace(:admin, :path=>"super_admin") do |admin|
|
|
292
|
+
# admin.resources :accounts
|
|
293
|
+
# end
|
|
294
|
+
#---
|
|
295
|
+
# @public
|
|
296
|
+
def namespace(name_or_path, options={}, &block)
|
|
297
|
+
path = options[:path] || name_or_path.to_s
|
|
298
|
+
(path.empty? ? self : match("/#{path}")).to(:namespace => name_or_path.to_s) do |r|
|
|
299
|
+
yield r
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Behavior#+resources+ is a route helper for defining a collection of
|
|
304
|
+
# RESTful resources. It yields to a block for child routes.
|
|
305
|
+
#
|
|
306
|
+
# ==== Parameters
|
|
307
|
+
# name<String, Symbol>:: The name of the resources
|
|
308
|
+
# options<Hash>::
|
|
309
|
+
# Ovverides and parameters to be associated with the route
|
|
310
|
+
#
|
|
311
|
+
# ==== Options (options)
|
|
312
|
+
# :namespace<~to_s>: The namespace for this route.
|
|
313
|
+
# :name_prefix<~to_s>:
|
|
314
|
+
# A prefix for the named routes. If a namespace is passed and there
|
|
315
|
+
# isn't a name prefix, the namespace will become the prefix.
|
|
316
|
+
# :controller<~to_s>: The controller for this route
|
|
317
|
+
# :collection<~to_s>: Special settings for the collections routes
|
|
318
|
+
# :member<Hash>:
|
|
319
|
+
# Special settings and resources related to a specific member of this
|
|
320
|
+
# resource.
|
|
321
|
+
#
|
|
322
|
+
# ==== Block parameters
|
|
323
|
+
# next_level<Behavior>:: The child behavior.
|
|
324
|
+
#
|
|
325
|
+
# ==== Returns
|
|
326
|
+
# Array::
|
|
327
|
+
# Routes which will define the specified RESTful collection of resources
|
|
328
|
+
#
|
|
329
|
+
# ==== Examples
|
|
330
|
+
#
|
|
331
|
+
# r.resources :posts # will result in the typical RESTful CRUD
|
|
332
|
+
# # GET /posts/?(\.:format)? :action => "index"
|
|
333
|
+
# # GET /posts/index(\.:format)? :action => "index"
|
|
334
|
+
# # GET /posts/new :action => "new"
|
|
335
|
+
# # POST /posts/?(\.:format)?, :action => "create"
|
|
336
|
+
# # GET /posts/:id(\.:format)? :action => "show"
|
|
337
|
+
# # GET /posts/:id[;/]edit :action => "edit"
|
|
338
|
+
# # PUT /posts/:id(\.:format)? :action => "update"
|
|
339
|
+
# # DELETE /posts/:id(\.:format)? :action => "destroy"
|
|
340
|
+
#
|
|
341
|
+
# # Nesting resources
|
|
342
|
+
# r.resources :posts do |posts|
|
|
343
|
+
# posts.resources :comments
|
|
344
|
+
# end
|
|
345
|
+
#---
|
|
346
|
+
# @public
|
|
347
|
+
def resources(name, options = {})
|
|
348
|
+
namespace = options[:namespace] || merged_params[:namespace]
|
|
349
|
+
|
|
350
|
+
next_level = match "/#{name}"
|
|
351
|
+
|
|
352
|
+
name_prefix = options.delete :name_prefix
|
|
353
|
+
|
|
354
|
+
if name_prefix.nil? && !namespace.nil?
|
|
355
|
+
name_prefix = namespace_to_name_prefix namespace
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
unless @@parent_resource.empty?
|
|
359
|
+
parent_resource = namespace_to_name_prefix @@parent_resource.join('_')
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
options[:controller] ||= merged_params[:controller] || name.to_s
|
|
363
|
+
|
|
364
|
+
singular = name.to_s.singularize
|
|
365
|
+
|
|
366
|
+
route_plural_name = "#{name_prefix}#{parent_resource}#{name}"
|
|
367
|
+
route_singular_name = "#{name_prefix}#{parent_resource}#{singular}"
|
|
368
|
+
|
|
369
|
+
behaviors = []
|
|
370
|
+
|
|
371
|
+
if member = options.delete(:member)
|
|
372
|
+
member.each_pair do |action, methods|
|
|
373
|
+
behaviors << Behavior.new(
|
|
374
|
+
{ :path => %r{^/:id[/;]#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
|
|
375
|
+
{ :action => action.to_s }, next_level
|
|
376
|
+
)
|
|
377
|
+
next_level.match("/:id/#{action}").to_route.name(:"#{action}_#{route_singular_name}")
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
if collection = options.delete(:collection)
|
|
382
|
+
collection.each_pair do |action, methods|
|
|
383
|
+
behaviors << Behavior.new(
|
|
384
|
+
{ :path => %r{^[/;]#{action}(\.:format)?$}, :method => /^(#{[methods].flatten * '|'})$/ },
|
|
385
|
+
{ :action => action.to_s }, next_level
|
|
386
|
+
)
|
|
387
|
+
next_level.match("/#{action}").to_route.name(:"#{action}_#{route_plural_name}")
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
routes = many_behaviors_to(behaviors + next_level.send(:resources_behaviors), options)
|
|
392
|
+
|
|
393
|
+
# Add names to some routes
|
|
394
|
+
[['', :"#{route_plural_name}"],
|
|
395
|
+
['/:id', :"#{route_singular_name}"],
|
|
396
|
+
['/new', :"new_#{route_singular_name}"],
|
|
397
|
+
['/:id/edit', :"edit_#{route_singular_name}"],
|
|
398
|
+
['/:id/delete', :"delete_#{route_singular_name}"]
|
|
399
|
+
].each do |path,name|
|
|
400
|
+
next_level.match(path).to_route.name(name)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
if block_given?
|
|
404
|
+
@@parent_resource.push(singular)
|
|
405
|
+
yield next_level.match("/:#{singular}_id")
|
|
406
|
+
@@parent_resource.pop
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
routes
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# Behavior#+resource+ is a route helper for defining a singular RESTful
|
|
413
|
+
# resource. It yields to a block for child routes.
|
|
414
|
+
#
|
|
415
|
+
# ==== Parameters
|
|
416
|
+
# name<String, Symbol>:: The name of the resource.
|
|
417
|
+
# options<Hash>::
|
|
418
|
+
# Overides and parameters to be associated with the route.
|
|
419
|
+
#
|
|
420
|
+
# ==== Options (options)
|
|
421
|
+
# :namespace<~to_s>: The namespace for this route.
|
|
422
|
+
# :name_prefix<~to_s>:
|
|
423
|
+
# A prefix for the named routes. If a namespace is passed and there
|
|
424
|
+
# isn't a name prefix, the namespace will become the prefix.
|
|
425
|
+
# :controller<~to_s>: The controller for this route
|
|
426
|
+
#
|
|
427
|
+
# ==== Block parameters
|
|
428
|
+
# next_level<Behavior>:: The child behavior.
|
|
429
|
+
#
|
|
430
|
+
# ==== Returns
|
|
431
|
+
# Array:: Routes which define a RESTful single resource.
|
|
432
|
+
#
|
|
433
|
+
# ==== Examples
|
|
434
|
+
#
|
|
435
|
+
# r.resources :account # will result in the typical RESTful CRUD
|
|
436
|
+
# # GET /account/new :action => "new"
|
|
437
|
+
# # POST /account/?(\.:format)?, :action => "create"
|
|
438
|
+
# # GET /account/(\.:format)? :action => "show"
|
|
439
|
+
# # GET /account/[;/]edit :action => "edit"
|
|
440
|
+
# # PUT /account/(\.:format)? :action => "update"
|
|
441
|
+
# # DELETE /account/(\.:format)? :action => "destroy"
|
|
442
|
+
#
|
|
443
|
+
# You can optionally pass :namespace and :controller to refine the routing
|
|
444
|
+
# or pass a block to nest resources.
|
|
445
|
+
#
|
|
446
|
+
# r.resource :account, :namespace => "admin" do |account|
|
|
447
|
+
# account.resources :preferences, :controller => "settings"
|
|
448
|
+
# end
|
|
449
|
+
# ---
|
|
450
|
+
# @public
|
|
451
|
+
def resource(name, options = {})
|
|
452
|
+
namespace = options[:namespace] || merged_params[:namespace]
|
|
453
|
+
|
|
454
|
+
next_level = match "/#{name}"
|
|
455
|
+
|
|
456
|
+
options[:controller] ||= merged_params[:controller] || name.to_s
|
|
457
|
+
|
|
458
|
+
# Do not pass :name_prefix option on to to_resource
|
|
459
|
+
name_prefix = options.delete :name_prefix
|
|
460
|
+
|
|
461
|
+
if name_prefix.nil? && !namespace.nil?
|
|
462
|
+
name_prefix = namespace_to_name_prefix namespace
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
unless @@parent_resource.empty?
|
|
466
|
+
parent_resource = namespace_to_name_prefix @@parent_resource.join('_')
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
routes = next_level.to_resource options
|
|
470
|
+
|
|
471
|
+
route_name = "#{name_prefix}#{name}"
|
|
472
|
+
|
|
473
|
+
next_level.match('').to_route.name(:"#{route_name}")
|
|
474
|
+
next_level.match('/new').to_route.name(:"new_#{route_name}")
|
|
475
|
+
next_level.match('/edit').to_route.name(:"edit_#{route_name}")
|
|
476
|
+
next_level.match('/delete').to_route.name(:"delete_#{route_name}")
|
|
477
|
+
|
|
478
|
+
if block_given?
|
|
479
|
+
@@parent_resource.push(route_name)
|
|
480
|
+
yield next_level
|
|
481
|
+
@@parent_resource.pop
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
routes
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# ==== Parameters
|
|
488
|
+
# params<Hash>:: Optional params for generating the RESTful routes.
|
|
489
|
+
# &block:: Optional block for the route generation.
|
|
490
|
+
#
|
|
491
|
+
# ==== Returns
|
|
492
|
+
# Array:: Routes matching the RESTful resource.
|
|
493
|
+
def to_resources(params = {}, &block)
|
|
494
|
+
many_behaviors_to resources_behaviors, params, &block
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# ==== Parameters
|
|
498
|
+
# params<Hash>:: Optional params for generating the RESTful routes.
|
|
499
|
+
# &block:: Optional block for the route generation.
|
|
500
|
+
#
|
|
501
|
+
# ==== Returns
|
|
502
|
+
# Array:: Routes matching the RESTful singular resource.
|
|
503
|
+
def to_resource(params = {}, &block)
|
|
504
|
+
many_behaviors_to resource_behaviors, params, &block
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
# ==== Returns
|
|
508
|
+
# Hash::
|
|
509
|
+
# The original conditions of this behavior merged with the original
|
|
510
|
+
# conditions of all its ancestors.
|
|
511
|
+
def merged_original_conditions
|
|
512
|
+
if parent.nil?
|
|
513
|
+
@original_conditions
|
|
514
|
+
else
|
|
515
|
+
merged_so_far = parent.merged_original_conditions
|
|
516
|
+
if path = Behavior.concat_without_endcaps(merged_so_far[:path], @original_conditions[:path])
|
|
517
|
+
merged_so_far.merge(@original_conditions).merge(:path => path)
|
|
518
|
+
else
|
|
519
|
+
merged_so_far.merge(@original_conditions)
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# ==== Returns
|
|
525
|
+
# Hash::
|
|
526
|
+
# The conditions of this behavior merged with the conditions of all its
|
|
527
|
+
# ancestors.
|
|
528
|
+
def merged_conditions
|
|
529
|
+
if parent.nil?
|
|
530
|
+
@conditions
|
|
531
|
+
else
|
|
532
|
+
merged_so_far = parent.merged_conditions
|
|
533
|
+
if path = Behavior.concat_without_endcaps(merged_so_far[:path], @conditions[:path])
|
|
534
|
+
merged_so_far.merge(@conditions).merge(:path => path)
|
|
535
|
+
else
|
|
536
|
+
merged_so_far.merge(@conditions)
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# ==== Returns
|
|
542
|
+
# Hash::
|
|
543
|
+
# The params of this behavior merged with the params of all its
|
|
544
|
+
# ancestors.
|
|
545
|
+
def merged_params
|
|
546
|
+
if parent.nil?
|
|
547
|
+
@params
|
|
548
|
+
else
|
|
549
|
+
parent.merged_params.merge(@params)
|
|
550
|
+
end
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# ==== Returns
|
|
554
|
+
# Hash::
|
|
555
|
+
# The route placeholders, e.g. :controllers, of this behavior merged
|
|
556
|
+
# with the placeholders of all its ancestors.
|
|
557
|
+
def merged_placeholders
|
|
558
|
+
placeholders = {}
|
|
559
|
+
(ancestors.reverse + [self]).each do |a|
|
|
560
|
+
a.placeholders.each_pair do |k, pair|
|
|
561
|
+
param, place = pair
|
|
562
|
+
placeholders[k] = [param, place + (param == :path ? a.total_previous_captures : 0)]
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
placeholders
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# ==== Returns
|
|
569
|
+
# String:: A human readable form of the behavior.
|
|
570
|
+
def inspect
|
|
571
|
+
"[captures: #{path_captures.inspect}, conditions: #{@original_conditions.inspect}, params: #{@params.inspect}, placeholders: #{@placeholders.inspect}]"
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
# ==== Returns
|
|
575
|
+
# Boolean:: True if this behavior has a regexp.
|
|
576
|
+
def regexp?
|
|
577
|
+
@conditions_have_regexp
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
protected
|
|
581
|
+
|
|
582
|
+
# ==== Parameters
|
|
583
|
+
# name_or_path<~to_s>::
|
|
584
|
+
# The name or path to convert to a form suitable for a prefix.
|
|
585
|
+
#
|
|
586
|
+
# ==== Returns
|
|
587
|
+
# String:: The prefix.
|
|
588
|
+
def namespace_to_name_prefix(name_or_path)
|
|
589
|
+
name_or_path.to_s.tr('/', '_') + '_'
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# ==== Parameters
|
|
593
|
+
# parent<Merb::Router::Behavior>::
|
|
594
|
+
# The parent behavior for the generated resource behaviors.
|
|
595
|
+
#
|
|
596
|
+
# ==== Returns
|
|
597
|
+
# Array:: Behaviors for a RESTful resource.
|
|
598
|
+
def resources_behaviors(parent = self)
|
|
599
|
+
[
|
|
600
|
+
Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :get }, { :action => "index" }, parent),
|
|
601
|
+
Behavior.new({ :path => %r[^/index(\.:format)?$], :method => :get }, { :action => "index" }, parent),
|
|
602
|
+
Behavior.new({ :path => %r[^/new$], :method => :get }, { :action => "new" }, parent),
|
|
603
|
+
Behavior.new({ :path => %r[^/?(\.:format)?$], :method => :post }, { :action => "create" }, parent),
|
|
604
|
+
Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :get }, { :action => "show" }, parent),
|
|
605
|
+
Behavior.new({ :path => %r[^/:id[;/]edit$], :method => :get }, { :action => "edit" }, parent),
|
|
606
|
+
Behavior.new({ :path => %r[^/:id[;/]delete$], :method => :get }, { :action => "delete" }, parent),
|
|
607
|
+
Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :put }, { :action => "update" }, parent),
|
|
608
|
+
Behavior.new({ :path => %r[^/:id(\.:format)?$], :method => :delete }, { :action => "destroy" }, parent)
|
|
609
|
+
]
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# ==== Parameters
|
|
613
|
+
# parent<Merb::Router::Behavior>::
|
|
614
|
+
# The parent behavior for the generated resource behaviors.
|
|
615
|
+
#
|
|
616
|
+
# ==== Returns
|
|
617
|
+
# Array:: Behaviors for a singular RESTful resource.
|
|
618
|
+
def resource_behaviors(parent = self)
|
|
619
|
+
[
|
|
620
|
+
Behavior.new({ :path => %r{^[;/]new$}, :method => :get }, { :action => "new" }, parent),
|
|
621
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :post }, { :action => "create" }, parent),
|
|
622
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :get }, { :action => "show" }, parent),
|
|
623
|
+
Behavior.new({ :path => %r{^[;/]edit$}, :method => :get }, { :action => "edit" }, parent),
|
|
624
|
+
Behavior.new({ :path => %r{^[;/]delete$}, :method => :get }, { :action => "delete" }, parent),
|
|
625
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :put }, { :action => "update" }, parent),
|
|
626
|
+
Behavior.new({ :path => %r{^/?(\.:format)?$}, :method => :delete }, { :action => "destroy" }, parent)
|
|
627
|
+
]
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
# ==== Parameters
|
|
631
|
+
# behaviors<Array>:: The behaviors to create routes from.
|
|
632
|
+
# params<Hash>:: Optional params for the route generation.
|
|
633
|
+
# &conditional_block:: Optional block for the route generation.
|
|
634
|
+
#
|
|
635
|
+
# ==== Returns
|
|
636
|
+
# Array:: The routes matching the behaviors.
|
|
637
|
+
def many_behaviors_to(behaviors, params = {}, &conditional_block)
|
|
638
|
+
behaviors.map { |b| b.to params, &conditional_block }
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
# Convert conditions to regular expression string sources for consistency.
|
|
642
|
+
def stringify_conditions
|
|
643
|
+
@conditions_have_regexp = false
|
|
644
|
+
@conditions.each_pair do |k,v|
|
|
645
|
+
# TODO: Other Regexp special chars
|
|
646
|
+
|
|
647
|
+
@conditions[k] = case v
|
|
648
|
+
when String,Symbol
|
|
649
|
+
"^#{v.to_s.escape_regexp}$"
|
|
650
|
+
when Regexp
|
|
651
|
+
@conditions_have_regexp = true
|
|
652
|
+
v.source
|
|
653
|
+
end
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
# Store the conditions as original conditions.
|
|
658
|
+
def copy_original_conditions
|
|
659
|
+
@original_conditions = {}
|
|
660
|
+
@conditions.each_pair do |key, value|
|
|
661
|
+
@original_conditions[key] = value.dup
|
|
662
|
+
end
|
|
663
|
+
@original_conditions
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
# Calculate the behaviors from the conditions and store them.
|
|
667
|
+
def deduce_placeholders
|
|
668
|
+
@conditions.each_pair do |match_key, source|
|
|
669
|
+
while match = SEGMENT_REGEXP.match(source)
|
|
670
|
+
source.sub! SEGMENT_REGEXP, PARENTHETICAL_SEGMENT_STRING
|
|
671
|
+
unless match[2] == ':' # No need to store anonymous place holders
|
|
672
|
+
placeholder_key = match[2].intern
|
|
673
|
+
@params[placeholder_key] = "#{match[1]}"
|
|
674
|
+
@placeholders[placeholder_key] = [
|
|
675
|
+
match_key, Behavior.count_parens_up_to(source, match.offset(1)[0])
|
|
676
|
+
]
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
# ==== Parameters
|
|
683
|
+
# list<Array>:: A list to which the ancestors should be added.
|
|
684
|
+
#
|
|
685
|
+
# ==== Returns
|
|
686
|
+
# Array:: All the ancestor behaviors of this behavior.
|
|
687
|
+
def ancestors(list = [])
|
|
688
|
+
if parent.nil?
|
|
689
|
+
list
|
|
690
|
+
else
|
|
691
|
+
list.push parent
|
|
692
|
+
parent.ancestors list
|
|
693
|
+
list
|
|
694
|
+
end
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
# ==== Returns
|
|
698
|
+
# Fixnum:: Number of regexp captures in the :path condition.
|
|
699
|
+
def path_captures
|
|
700
|
+
return 0 unless conditions[:path]
|
|
701
|
+
Behavior.count_parens_up_to(conditions[:path], conditions[:path].size)
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
# ==== Returns
|
|
705
|
+
# Fixnum:: Total number of previous path captures.
|
|
706
|
+
def total_previous_captures
|
|
707
|
+
ancestors.map{|a| a.path_captures}.inject(0){|sum, n| sum + n}
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# def merge_with_ancestors
|
|
711
|
+
# self.class.new(merged_conditions, merged_params)
|
|
712
|
+
# end
|
|
713
|
+
|
|
714
|
+
# ==== Parameters
|
|
715
|
+
# conditions<Hash>::
|
|
716
|
+
# The conditions to compile. Defaults to merged_conditions.
|
|
717
|
+
#
|
|
718
|
+
# ==== Returns
|
|
719
|
+
# Hash:: The compiled conditions, with each value as a Regexp object.
|
|
720
|
+
def compiled_conditions(conditions = merged_conditions)
|
|
721
|
+
conditions.inject({}) do |compiled,(k,v)|
|
|
722
|
+
compiled.merge k => Regexp.new(v)
|
|
723
|
+
end
|
|
724
|
+
end
|
|
725
|
+
|
|
726
|
+
# ==== Parameters
|
|
727
|
+
# params<Hash>:: The params to compile. Defaults to merged_params.
|
|
728
|
+
# placeholders<Hash>::
|
|
729
|
+
# The route placeholders for this behavior. Defaults to
|
|
730
|
+
# merged_placeholders.
|
|
731
|
+
#
|
|
732
|
+
# ==== Returns
|
|
733
|
+
# String:: The params hash in an eval'able form.
|
|
734
|
+
#
|
|
735
|
+
# ==== Examples
|
|
736
|
+
# compiled_params({ :controller => "admin/:controller" })
|
|
737
|
+
# # => { :controller => "'admin/' + matches[:path][1]" }
|
|
738
|
+
#
|
|
739
|
+
def compiled_params(params = merged_params, placeholders = merged_placeholders)
|
|
740
|
+
compiled = {}
|
|
741
|
+
params.each_pair do |key, value|
|
|
742
|
+
unless value.is_a? String
|
|
743
|
+
raise ArgumentError, "param value must be string (#{value.inspect})"
|
|
744
|
+
end
|
|
745
|
+
result = []
|
|
746
|
+
value = value.dup
|
|
747
|
+
match = true
|
|
748
|
+
while match
|
|
749
|
+
if match = SEGMENT_REGEXP_WITH_BRACKETS.match(value)
|
|
750
|
+
result << match.pre_match unless match.pre_match.empty?
|
|
751
|
+
ph_key = match[1][1..-1].intern
|
|
752
|
+
if match[2] # has brackets, e.g. :path[2]
|
|
753
|
+
result << :"#{ph_key}#{match[3]}"
|
|
754
|
+
else # no brackets, e.g. a named placeholder such as :controller
|
|
755
|
+
if place = placeholders[ph_key]
|
|
756
|
+
result << :"#{place[0]}#{place[1]}"
|
|
757
|
+
else
|
|
758
|
+
raise "Placeholder not found while compiling routes: :#{ph_key}"
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
value = match.post_match
|
|
762
|
+
elsif match = JUST_BRACKETS.match(value)
|
|
763
|
+
# This is a reference to :path
|
|
764
|
+
result << match.pre_match unless match.pre_match.empty?
|
|
765
|
+
result << :"path#{match[1]}"
|
|
766
|
+
value = match.post_match
|
|
767
|
+
else
|
|
768
|
+
result << value unless value.empty?
|
|
769
|
+
end
|
|
770
|
+
end
|
|
771
|
+
compiled[key] = Behavior.array_to_code(result).gsub("\\_", "_")
|
|
772
|
+
end
|
|
773
|
+
compiled
|
|
774
|
+
end
|
|
775
|
+
end # Behavior
|
|
776
|
+
end
|
|
777
|
+
end
|