actionpack 3.2.22.5 → 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +641 -418
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -288
- data/lib/abstract_controller.rb +1 -8
- data/lib/abstract_controller/asset_paths.rb +2 -2
- data/lib/abstract_controller/base.rb +39 -37
- data/lib/abstract_controller/callbacks.rb +101 -82
- data/lib/abstract_controller/collector.rb +7 -3
- data/lib/abstract_controller/helpers.rb +23 -11
- data/lib/abstract_controller/layouts.rb +68 -73
- data/lib/abstract_controller/logger.rb +1 -2
- data/lib/abstract_controller/rendering.rb +22 -13
- data/lib/abstract_controller/translation.rb +16 -1
- data/lib/abstract_controller/url_for.rb +6 -6
- data/lib/abstract_controller/view_paths.rb +1 -1
- data/lib/action_controller.rb +15 -6
- data/lib/action_controller/base.rb +46 -22
- data/lib/action_controller/caching.rb +46 -33
- data/lib/action_controller/caching/fragments.rb +23 -53
- data/lib/action_controller/deprecated.rb +5 -1
- data/lib/action_controller/deprecated/integration_test.rb +3 -0
- data/lib/action_controller/log_subscriber.rb +11 -8
- data/lib/action_controller/metal.rb +16 -30
- data/lib/action_controller/metal/conditional_get.rb +76 -32
- data/lib/action_controller/metal/data_streaming.rb +20 -26
- data/lib/action_controller/metal/exceptions.rb +19 -6
- data/lib/action_controller/metal/flash.rb +24 -9
- data/lib/action_controller/metal/force_ssl.rb +32 -9
- data/lib/action_controller/metal/head.rb +25 -4
- data/lib/action_controller/metal/helpers.rb +6 -9
- data/lib/action_controller/metal/hide_actions.rb +1 -2
- data/lib/action_controller/metal/http_authentication.rb +105 -87
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +2 -1
- data/lib/action_controller/metal/live.rb +141 -0
- data/lib/action_controller/metal/mime_responds.rb +161 -47
- data/lib/action_controller/metal/params_wrapper.rb +112 -74
- data/lib/action_controller/metal/rack_delegation.rb +9 -3
- data/lib/action_controller/metal/redirecting.rb +15 -20
- data/lib/action_controller/metal/renderers.rb +11 -9
- data/lib/action_controller/metal/rendering.rb +8 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +112 -19
- data/lib/action_controller/metal/responder.rb +20 -19
- data/lib/action_controller/metal/streaming.rb +12 -18
- data/lib/action_controller/metal/strong_parameters.rb +516 -0
- data/lib/action_controller/metal/testing.rb +13 -18
- data/lib/action_controller/metal/url_for.rb +27 -25
- data/lib/action_controller/model_naming.rb +12 -0
- data/lib/action_controller/railtie.rb +33 -17
- data/lib/action_controller/railties/helpers.rb +22 -0
- data/lib/action_controller/record_identifier.rb +18 -72
- data/lib/action_controller/test_case.rb +215 -123
- data/lib/action_controller/vendor/html-scanner.rb +4 -19
- data/lib/action_dispatch.rb +27 -19
- data/lib/action_dispatch/http/cache.rb +63 -11
- data/lib/action_dispatch/http/filter_parameters.rb +18 -8
- data/lib/action_dispatch/http/filter_redirect.rb +37 -0
- data/lib/action_dispatch/http/headers.rb +27 -19
- data/lib/action_dispatch/http/mime_negotiation.rb +25 -2
- data/lib/action_dispatch/http/mime_type.rb +145 -113
- data/lib/action_dispatch/http/mime_types.rb +1 -1
- data/lib/action_dispatch/http/parameter_filter.rb +44 -46
- data/lib/action_dispatch/http/parameters.rb +12 -5
- data/lib/action_dispatch/http/rack_cache.rb +2 -3
- data/lib/action_dispatch/http/request.rb +49 -18
- data/lib/action_dispatch/http/response.rb +129 -35
- data/lib/action_dispatch/http/upload.rb +60 -17
- data/lib/action_dispatch/http/url.rb +53 -31
- data/lib/action_dispatch/journey.rb +5 -0
- data/lib/action_dispatch/journey/backwards.rb +5 -0
- data/lib/action_dispatch/journey/formatter.rb +146 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +44 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +156 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
- data/lib/action_dispatch/journey/nodes/node.rb +124 -0
- data/lib/action_dispatch/journey/parser.rb +206 -0
- data/lib/action_dispatch/journey/parser.y +47 -0
- data/lib/action_dispatch/journey/parser_extras.rb +23 -0
- data/lib/action_dispatch/journey/path/pattern.rb +196 -0
- data/lib/action_dispatch/journey/route.rb +116 -0
- data/lib/action_dispatch/journey/router.rb +164 -0
- data/lib/action_dispatch/journey/router/strexp.rb +24 -0
- data/lib/action_dispatch/journey/router/utils.rb +54 -0
- data/lib/action_dispatch/journey/routes.rb +75 -0
- data/lib/action_dispatch/journey/scanner.rb +61 -0
- data/lib/action_dispatch/journey/visitors.rb +189 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +34 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/middleware/callbacks.rb +9 -4
- data/lib/action_dispatch/middleware/cookies.rb +168 -57
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -17
- data/lib/action_dispatch/middleware/exception_wrapper.rb +27 -3
- data/lib/action_dispatch/middleware/flash.rb +58 -58
- data/lib/action_dispatch/middleware/params_parser.rb +14 -29
- data/lib/action_dispatch/middleware/public_exceptions.rb +31 -14
- data/lib/action_dispatch/middleware/reloader.rb +6 -6
- data/lib/action_dispatch/middleware/remote_ip.rb +145 -39
- data/lib/action_dispatch/middleware/request_id.rb +2 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
- data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +81 -7
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +12 -45
- data/lib/action_dispatch/middleware/ssl.rb +70 -0
- data/lib/action_dispatch/middleware/stack.rb +6 -1
- data/lib/action_dispatch/middleware/static.rb +5 -24
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +14 -11
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +25 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +3 -3
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +15 -9
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -5
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +7 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +30 -15
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +39 -13
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +6 -2
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +144 -0
- data/lib/action_dispatch/railtie.rb +16 -6
- data/lib/action_dispatch/request/session.rb +181 -0
- data/lib/action_dispatch/routing.rb +41 -40
- data/lib/action_dispatch/routing/inspector.rb +240 -0
- data/lib/action_dispatch/routing/mapper.rb +501 -273
- data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -20
- data/lib/action_dispatch/routing/redirection.rb +46 -29
- data/lib/action_dispatch/routing/route_set.rb +203 -164
- data/lib/action_dispatch/routing/routes_proxy.rb +2 -0
- data/lib/action_dispatch/routing/url_for.rb +48 -33
- data/lib/action_dispatch/testing/assertions/dom.rb +3 -13
- data/lib/action_dispatch/testing/assertions/response.rb +32 -40
- data/lib/action_dispatch/testing/assertions/routing.rb +40 -39
- data/lib/action_dispatch/testing/assertions/selector.rb +15 -20
- data/lib/action_dispatch/testing/assertions/tag.rb +20 -23
- data/lib/action_dispatch/testing/integration.rb +41 -22
- data/lib/action_dispatch/testing/test_process.rb +9 -6
- data/lib/action_dispatch/testing/test_request.rb +7 -3
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/version.rb +4 -4
- data/lib/action_view.rb +17 -8
- data/lib/action_view/base.rb +15 -34
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/context.rb +4 -4
- data/lib/action_view/dependency_tracker.rb +91 -0
- data/lib/action_view/digestor.rb +85 -0
- data/lib/action_view/flows.rb +1 -4
- data/lib/action_view/helpers.rb +2 -4
- data/lib/action_view/helpers/active_model_helper.rb +3 -4
- data/lib/action_view/helpers/asset_tag_helper.rb +211 -353
- data/lib/action_view/helpers/asset_url_helper.rb +354 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +13 -10
- data/lib/action_view/helpers/cache_helper.rb +150 -18
- data/lib/action_view/helpers/capture_helper.rb +42 -29
- data/lib/action_view/helpers/csrf_helper.rb +0 -2
- data/lib/action_view/helpers/date_helper.rb +268 -247
- data/lib/action_view/helpers/debug_helper.rb +10 -11
- data/lib/action_view/helpers/form_helper.rb +904 -547
- data/lib/action_view/helpers/form_options_helper.rb +341 -166
- data/lib/action_view/helpers/form_tag_helper.rb +188 -88
- data/lib/action_view/helpers/javascript_helper.rb +23 -16
- data/lib/action_view/helpers/number_helper.rb +148 -354
- data/lib/action_view/helpers/output_safety_helper.rb +3 -3
- data/lib/action_view/helpers/record_tag_helper.rb +17 -22
- data/lib/action_view/helpers/rendering_helper.rb +2 -4
- data/lib/action_view/helpers/sanitize_helper.rb +3 -6
- data/lib/action_view/helpers/tag_helper.rb +43 -37
- data/lib/action_view/helpers/tags.rb +39 -0
- data/lib/action_view/helpers/tags/base.rb +148 -0
- data/lib/action_view/helpers/tags/check_box.rb +64 -0
- data/lib/action_view/helpers/tags/checkable.rb +16 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +43 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +83 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
- data/lib/action_view/helpers/tags/collection_select.rb +28 -0
- data/lib/action_view/helpers/tags/color_field.rb +25 -0
- data/lib/action_view/helpers/tags/date_field.rb +13 -0
- data/lib/action_view/helpers/tags/date_select.rb +72 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
- data/lib/action_view/helpers/tags/email_field.rb +8 -0
- data/lib/action_view/helpers/tags/file_field.rb +8 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
- data/lib/action_view/helpers/tags/label.rb +65 -0
- data/lib/action_view/helpers/tags/month_field.rb +13 -0
- data/lib/action_view/helpers/tags/number_field.rb +18 -0
- data/lib/action_view/helpers/tags/password_field.rb +12 -0
- data/lib/action_view/helpers/tags/radio_button.rb +31 -0
- data/lib/action_view/helpers/tags/range_field.rb +8 -0
- data/lib/action_view/helpers/tags/search_field.rb +24 -0
- data/lib/action_view/helpers/tags/select.rb +41 -0
- data/lib/action_view/helpers/tags/tel_field.rb +8 -0
- data/lib/action_view/helpers/tags/text_area.rb +18 -0
- data/lib/action_view/helpers/tags/text_field.rb +29 -0
- data/lib/action_view/helpers/tags/time_field.rb +13 -0
- data/lib/action_view/helpers/tags/time_select.rb +8 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
- data/lib/action_view/helpers/tags/url_field.rb +8 -0
- data/lib/action_view/helpers/tags/week_field.rb +13 -0
- data/lib/action_view/helpers/text_helper.rb +126 -113
- data/lib/action_view/helpers/translation_helper.rb +32 -16
- data/lib/action_view/helpers/url_helper.rb +200 -271
- data/lib/action_view/locale/en.yml +1 -105
- data/lib/action_view/log_subscriber.rb +6 -4
- data/lib/action_view/lookup_context.rb +15 -39
- data/lib/action_view/model_naming.rb +12 -0
- data/lib/action_view/path_set.rb +9 -39
- data/lib/action_view/railtie.rb +6 -22
- data/lib/action_view/record_identifier.rb +84 -0
- data/lib/action_view/renderer/abstract_renderer.rb +10 -19
- data/lib/action_view/renderer/partial_renderer.rb +144 -81
- data/lib/action_view/renderer/renderer.rb +2 -19
- data/lib/action_view/renderer/streaming_template_renderer.rb +2 -5
- data/lib/action_view/renderer/template_renderer.rb +14 -13
- data/lib/action_view/routing_url_for.rb +107 -0
- data/lib/action_view/template.rb +22 -21
- data/lib/action_view/template/error.rb +22 -12
- data/lib/action_view/template/handlers.rb +12 -9
- data/lib/action_view/template/handlers/builder.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +11 -16
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/resolver.rb +111 -83
- data/lib/action_view/template/text.rb +12 -8
- data/lib/action_view/template/types.rb +57 -0
- data/lib/action_view/test_case.rb +66 -43
- data/lib/action_view/testing/resolvers.rb +3 -2
- data/lib/action_view/vendor/html-scanner.rb +20 -0
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/document.rb +0 -0
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/node.rb +12 -12
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/sanitizer.rb +18 -7
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/selector.rb +1 -1
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/tokenizer.rb +1 -1
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/version.rb +0 -0
- metadata +135 -125
- data/lib/action_controller/caching/actions.rb +0 -185
- data/lib/action_controller/caching/pages.rb +0 -187
- data/lib/action_controller/caching/sweeping.rb +0 -97
- data/lib/action_controller/deprecated/performance_test.rb +0 -1
- data/lib/action_controller/metal/compatibility.rb +0 -65
- data/lib/action_controller/metal/session_management.rb +0 -14
- data/lib/action_controller/railties/paths.rb +0 -25
- data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
- data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
- data/lib/action_dispatch/middleware/head.rb +0 -18
- data/lib/action_dispatch/middleware/rescue.rb +0 -26
- data/lib/action_dispatch/testing/performance_test.rb +0 -10
- data/lib/action_view/asset_paths.rb +0 -142
- data/lib/action_view/helpers/asset_paths.rb +0 -7
- data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
- data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
- data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
- data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
- data/lib/sprockets/assets.rake +0 -99
- data/lib/sprockets/bootstrap.rb +0 -37
- data/lib/sprockets/compressors.rb +0 -83
- data/lib/sprockets/helpers.rb +0 -6
- data/lib/sprockets/helpers/isolated_helper.rb +0 -13
- data/lib/sprockets/helpers/rails_helper.rb +0 -182
- data/lib/sprockets/railtie.rb +0 -62
- data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,3 +1,4 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
require 'active_support/core_ext/object/to_param'
|
2
3
|
require 'active_support/core_ext/regexp'
|
3
4
|
|
@@ -60,7 +61,7 @@ module ActionDispatch
|
|
60
61
|
# directory by using +scope+. +scope+ takes additional options which
|
61
62
|
# apply to all enclosed routes.
|
62
63
|
#
|
63
|
-
# scope :
|
64
|
+
# scope path: "/cpanel", as: 'admin' do
|
64
65
|
# resources :posts, :comments
|
65
66
|
# end
|
66
67
|
#
|
@@ -77,22 +78,22 @@ module ActionDispatch
|
|
77
78
|
# Example:
|
78
79
|
#
|
79
80
|
# # In routes.rb
|
80
|
-
# match '/login' => 'accounts#login', :
|
81
|
+
# match '/login' => 'accounts#login', as: 'login'
|
81
82
|
#
|
82
83
|
# # With render, redirect_to, tests, etc.
|
83
84
|
# redirect_to login_url
|
84
85
|
#
|
85
86
|
# Arguments can be passed as well.
|
86
87
|
#
|
87
|
-
# redirect_to show_item_path(:
|
88
|
+
# redirect_to show_item_path(id: 25)
|
88
89
|
#
|
89
90
|
# Use <tt>root</tt> as a shorthand to name a route for the root path "/".
|
90
91
|
#
|
91
92
|
# # In routes.rb
|
92
|
-
# root :
|
93
|
+
# root to: 'blogs#index'
|
93
94
|
#
|
94
95
|
# # would recognize http://www.example.com/ as
|
95
|
-
# params = { :
|
96
|
+
# params = { controller: 'blogs', action: 'index' }
|
96
97
|
#
|
97
98
|
# # and provide these named routes
|
98
99
|
# root_url # => 'http://www.example.com/'
|
@@ -109,43 +110,43 @@ module ActionDispatch
|
|
109
110
|
# end
|
110
111
|
#
|
111
112
|
# # provides named routes for show, delete, and edit
|
112
|
-
# link_to @article.title, show_path(:
|
113
|
+
# link_to @article.title, show_path(id: @article.id)
|
113
114
|
#
|
114
115
|
# == Pretty URLs
|
115
116
|
#
|
116
117
|
# Routes can generate pretty URLs. For example:
|
117
118
|
#
|
118
|
-
# match '/articles/:year/:month/:day' => 'articles#find_by_id', :
|
119
|
-
# :
|
120
|
-
# :
|
121
|
-
# :
|
119
|
+
# match '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
|
120
|
+
# year: /\d{4}/,
|
121
|
+
# month: /\d{1,2}/,
|
122
|
+
# day: /\d{1,2}/
|
122
123
|
# }
|
123
124
|
#
|
124
125
|
# Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
|
125
126
|
# maps to
|
126
127
|
#
|
127
|
-
# params = {:
|
128
|
+
# params = {year: '2005', month: '11', day: '06'}
|
128
129
|
#
|
129
130
|
# == Regular Expressions and parameters
|
130
131
|
# You can specify a regular expression to define a format for a parameter.
|
131
132
|
#
|
132
133
|
# controller 'geocode' do
|
133
|
-
# match 'geocode/:postalcode' => :show, :
|
134
|
-
# :
|
134
|
+
# match 'geocode/:postalcode' => :show, constraints: {
|
135
|
+
# postalcode: /\d{5}(-\d{4})?/
|
135
136
|
# }
|
136
137
|
#
|
137
138
|
# Constraints can include the 'ignorecase' and 'extended syntax' regular
|
138
139
|
# expression modifiers:
|
139
140
|
#
|
140
141
|
# controller 'geocode' do
|
141
|
-
# match 'geocode/:postalcode' => :show, :
|
142
|
-
# :
|
142
|
+
# match 'geocode/:postalcode' => :show, constraints: {
|
143
|
+
# postalcode: /hx\d\d\s\d[a-z]{2}/i
|
143
144
|
# }
|
144
145
|
# end
|
145
146
|
#
|
146
147
|
# controller 'geocode' do
|
147
|
-
# match 'geocode/:postalcode' => :show, :
|
148
|
-
# :
|
148
|
+
# match 'geocode/:postalcode' => :show, constraints: {
|
149
|
+
# postalcode: /# Postcode format
|
149
150
|
# \d{5} #Prefix
|
150
151
|
# (-\d{4})? #Suffix
|
151
152
|
# /x
|
@@ -171,9 +172,9 @@ module ActionDispatch
|
|
171
172
|
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
|
172
173
|
# up with:
|
173
174
|
#
|
174
|
-
# params = { :
|
175
|
-
# :
|
176
|
-
# :
|
175
|
+
# params = { controller: 'blog',
|
176
|
+
# action: 'edit',
|
177
|
+
# id: '22'
|
177
178
|
# }
|
178
179
|
#
|
179
180
|
# By not relying on default routes, you improve the security of your
|
@@ -182,15 +183,16 @@ module ActionDispatch
|
|
182
183
|
#
|
183
184
|
# == HTTP Methods
|
184
185
|
#
|
185
|
-
# Using the <tt>:via</tt> option when specifying a route allows you to
|
186
|
-
# Possible values are <tt>:post</tt>,
|
187
|
-
#
|
188
|
-
#
|
186
|
+
# Using the <tt>:via</tt> option when specifying a route allows you to
|
187
|
+
# restrict it to a specific HTTP method. Possible values are <tt>:post</tt>,
|
188
|
+
# <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and
|
189
|
+
# <tt>:any</tt>. If your route needs to respond to more than one method you
|
190
|
+
# can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is
|
191
|
+
# <tt>:any</tt> which means that the route will respond to any of the HTTP
|
192
|
+
# methods.
|
189
193
|
#
|
190
|
-
#
|
191
|
-
#
|
192
|
-
# match 'post/:id' => 'posts#show', :via => :get
|
193
|
-
# match 'post/:id' => "posts#create_comment', :via => :post
|
194
|
+
# match 'post/:id' => 'posts#show', via: :get
|
195
|
+
# match 'post/:id' => 'posts#create_comment', via: :post
|
194
196
|
#
|
195
197
|
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
|
196
198
|
# URL will route to the <tt>show</tt> action.
|
@@ -198,12 +200,10 @@ module ActionDispatch
|
|
198
200
|
# === HTTP helper methods
|
199
201
|
#
|
200
202
|
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
|
201
|
-
# methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
|
202
|
-
#
|
203
|
-
# Examples:
|
203
|
+
# methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
|
204
204
|
#
|
205
205
|
# get 'post/:id' => 'posts#show'
|
206
|
-
# post 'post/:id' =>
|
206
|
+
# post 'post/:id' => 'posts#create_comment'
|
207
207
|
#
|
208
208
|
# This syntax is less verbose and the intention is more apparent to someone else reading your code,
|
209
209
|
# however if your route needs to respond to more than one HTTP method (or all methods) then using the
|
@@ -215,6 +215,12 @@ module ActionDispatch
|
|
215
215
|
#
|
216
216
|
# match "/stories" => redirect("/posts")
|
217
217
|
#
|
218
|
+
# == Unicode character routes
|
219
|
+
#
|
220
|
+
# You can specify unicode character routes in your router:
|
221
|
+
#
|
222
|
+
# match "こんにちは" => "welcome#index"
|
223
|
+
#
|
218
224
|
# == Routing to Rack Applications
|
219
225
|
#
|
220
226
|
# Instead of a String, like <tt>posts#index</tt>, which corresponds to the
|
@@ -239,7 +245,7 @@ module ActionDispatch
|
|
239
245
|
# === +assert_routing+
|
240
246
|
#
|
241
247
|
# def test_movie_route_properly_splits
|
242
|
-
# opts = {:
|
248
|
+
# opts = {controller: "plugin", action: "checkout", id: "2"}
|
243
249
|
# assert_routing "plugin/checkout/2", opts
|
244
250
|
# end
|
245
251
|
#
|
@@ -248,7 +254,7 @@ module ActionDispatch
|
|
248
254
|
# === +assert_recognizes+
|
249
255
|
#
|
250
256
|
# def test_route_has_options
|
251
|
-
# opts = {:
|
257
|
+
# opts = {controller: "plugin", action: "show", id: "12"}
|
252
258
|
# assert_recognizes opts, "/plugins/show/12"
|
253
259
|
# end
|
254
260
|
#
|
@@ -283,11 +289,6 @@ module ActionDispatch
|
|
283
289
|
autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes'
|
284
290
|
|
285
291
|
SEPARATORS = %w( / . ? ) #:nodoc:
|
286
|
-
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
|
287
|
-
|
288
|
-
# A helper module to hold URL related helpers.
|
289
|
-
module Helpers #:nodoc:
|
290
|
-
include PolymorphicRoutes
|
291
|
-
end
|
292
|
+
HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc:
|
292
293
|
end
|
293
294
|
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
require 'active_support/core_ext/string/strip'
|
3
|
+
|
4
|
+
module ActionDispatch
|
5
|
+
module Routing
|
6
|
+
class RouteWrapper < SimpleDelegator
|
7
|
+
def endpoint
|
8
|
+
rack_app ? rack_app.inspect : "#{controller}##{action}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def constraints
|
12
|
+
requirements.except(:controller, :action)
|
13
|
+
end
|
14
|
+
|
15
|
+
def rack_app(app = self.app)
|
16
|
+
@rack_app ||= begin
|
17
|
+
class_name = app.class.name.to_s
|
18
|
+
if class_name == "ActionDispatch::Routing::Mapper::Constraints"
|
19
|
+
rack_app(app.app)
|
20
|
+
elsif ActionDispatch::Routing::Redirect === app || class_name !~ /^ActionDispatch::Routing/
|
21
|
+
app
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def verb
|
27
|
+
super.source.gsub(/[$^]/, '')
|
28
|
+
end
|
29
|
+
|
30
|
+
def path
|
31
|
+
super.spec.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
super.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def regexp
|
39
|
+
__getobj__.path.to_regexp
|
40
|
+
end
|
41
|
+
|
42
|
+
def json_regexp
|
43
|
+
str = regexp.inspect.
|
44
|
+
sub('\\A' , '^').
|
45
|
+
sub('\\Z' , '$').
|
46
|
+
sub('\\z' , '$').
|
47
|
+
sub(/^\// , '').
|
48
|
+
sub(/\/[a-z]*$/ , '').
|
49
|
+
gsub(/\(\?#.+\)/ , '').
|
50
|
+
gsub(/\(\?-\w+:/ , '(').
|
51
|
+
gsub(/\s/ , '')
|
52
|
+
Regexp.new(str).source
|
53
|
+
end
|
54
|
+
|
55
|
+
def reqs
|
56
|
+
@reqs ||= begin
|
57
|
+
reqs = endpoint
|
58
|
+
reqs += " #{constraints.to_s}" unless constraints.empty?
|
59
|
+
reqs
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def controller
|
64
|
+
requirements[:controller] || ':controller'
|
65
|
+
end
|
66
|
+
|
67
|
+
def action
|
68
|
+
requirements[:action] || ':action'
|
69
|
+
end
|
70
|
+
|
71
|
+
def internal?
|
72
|
+
controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
|
73
|
+
end
|
74
|
+
|
75
|
+
def engine?
|
76
|
+
rack_app && rack_app.respond_to?(:routes)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# This class is just used for displaying route information when someone
|
82
|
+
# executes `rake routes` or looks at the RoutingError page.
|
83
|
+
# People should not use this class.
|
84
|
+
class RoutesInspector # :nodoc:
|
85
|
+
def initialize(routes)
|
86
|
+
@engines = {}
|
87
|
+
@routes = routes
|
88
|
+
end
|
89
|
+
|
90
|
+
def format(formatter, filter = nil)
|
91
|
+
routes_to_display = filter_routes(filter)
|
92
|
+
|
93
|
+
routes = collect_routes(routes_to_display)
|
94
|
+
|
95
|
+
if routes.none?
|
96
|
+
formatter.no_routes
|
97
|
+
return formatter.result
|
98
|
+
end
|
99
|
+
|
100
|
+
formatter.header routes
|
101
|
+
formatter.section routes
|
102
|
+
|
103
|
+
@engines.each do |name, engine_routes|
|
104
|
+
formatter.section_title "Routes for #{name}"
|
105
|
+
formatter.section engine_routes
|
106
|
+
end
|
107
|
+
|
108
|
+
formatter.result
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def filter_routes(filter)
|
114
|
+
if filter
|
115
|
+
@routes.select { |route| route.defaults[:controller] == filter }
|
116
|
+
else
|
117
|
+
@routes
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def collect_routes(routes)
|
122
|
+
routes.collect do |route|
|
123
|
+
RouteWrapper.new(route)
|
124
|
+
end.reject do |route|
|
125
|
+
route.internal?
|
126
|
+
end.collect do |route|
|
127
|
+
collect_engine_routes(route)
|
128
|
+
|
129
|
+
{ name: route.name,
|
130
|
+
verb: route.verb,
|
131
|
+
path: route.path,
|
132
|
+
reqs: route.reqs,
|
133
|
+
regexp: route.json_regexp }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def collect_engine_routes(route)
|
138
|
+
name = route.endpoint
|
139
|
+
return unless route.engine?
|
140
|
+
return if @engines[name]
|
141
|
+
|
142
|
+
routes = route.rack_app.routes
|
143
|
+
if routes.is_a?(ActionDispatch::Routing::RouteSet)
|
144
|
+
@engines[name] = collect_routes(routes.routes)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class ConsoleFormatter
|
150
|
+
def initialize
|
151
|
+
@buffer = []
|
152
|
+
end
|
153
|
+
|
154
|
+
def result
|
155
|
+
@buffer.join("\n")
|
156
|
+
end
|
157
|
+
|
158
|
+
def section_title(title)
|
159
|
+
@buffer << "\n#{title}:"
|
160
|
+
end
|
161
|
+
|
162
|
+
def section(routes)
|
163
|
+
@buffer << draw_section(routes)
|
164
|
+
end
|
165
|
+
|
166
|
+
def header(routes)
|
167
|
+
@buffer << draw_header(routes)
|
168
|
+
end
|
169
|
+
|
170
|
+
def no_routes
|
171
|
+
@buffer << <<-MESSAGE.strip_heredoc
|
172
|
+
You don't have any routes defined!
|
173
|
+
|
174
|
+
Please add some routes in config/routes.rb.
|
175
|
+
|
176
|
+
For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html.
|
177
|
+
MESSAGE
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
def draw_section(routes)
|
182
|
+
name_width, verb_width, path_width = widths(routes)
|
183
|
+
|
184
|
+
routes.map do |r|
|
185
|
+
"#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def draw_header(routes)
|
190
|
+
name_width, verb_width, path_width = widths(routes)
|
191
|
+
|
192
|
+
"#{"Prefix".rjust(name_width)} #{"Verb".ljust(verb_width)} #{"URI Pattern".ljust(path_width)} Controller#Action"
|
193
|
+
end
|
194
|
+
|
195
|
+
def widths(routes)
|
196
|
+
[routes.map { |r| r[:name].length }.max,
|
197
|
+
routes.map { |r| r[:verb].length }.max,
|
198
|
+
routes.map { |r| r[:path].length }.max]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class HtmlTableFormatter
|
203
|
+
def initialize(view)
|
204
|
+
@view = view
|
205
|
+
@buffer = []
|
206
|
+
end
|
207
|
+
|
208
|
+
def section_title(title)
|
209
|
+
@buffer << %(<tr><th colspan="4">#{title}</th></tr>)
|
210
|
+
end
|
211
|
+
|
212
|
+
def section(routes)
|
213
|
+
@buffer << @view.render(partial: "routes/route", collection: routes)
|
214
|
+
end
|
215
|
+
|
216
|
+
# the header is part of the HTML page, so we don't construct it here.
|
217
|
+
def header(routes)
|
218
|
+
end
|
219
|
+
|
220
|
+
def no_routes
|
221
|
+
@buffer << <<-MESSAGE.strip_heredoc
|
222
|
+
<p>You don't have any routes defined!</p>
|
223
|
+
<ul>
|
224
|
+
<li>Please add some routes in <tt>config/routes.rb</tt>.</li>
|
225
|
+
<li>
|
226
|
+
For more information about routes, please see the Rails guide
|
227
|
+
<a href="http://guides.rubyonrails.org/routing.html">Rails Routing from the Outside In</a>.
|
228
|
+
</li>
|
229
|
+
</ul>
|
230
|
+
MESSAGE
|
231
|
+
end
|
232
|
+
|
233
|
+
def result
|
234
|
+
@view.raw @view.render(layout: "routes/table") {
|
235
|
+
@view.raw @buffer.join("\n")
|
236
|
+
}
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
@@ -1,13 +1,16 @@
|
|
1
1
|
require 'active_support/core_ext/hash/except'
|
2
|
-
require 'active_support/core_ext/
|
3
|
-
require 'active_support/core_ext/
|
2
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
3
|
+
require 'active_support/core_ext/hash/slice'
|
4
4
|
require 'active_support/core_ext/enumerable'
|
5
|
+
require 'active_support/core_ext/array/extract_options'
|
5
6
|
require 'active_support/inflector'
|
6
7
|
require 'action_dispatch/routing/redirection'
|
7
8
|
|
8
9
|
module ActionDispatch
|
9
10
|
module Routing
|
10
11
|
class Mapper
|
12
|
+
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
13
|
+
|
11
14
|
class Constraints #:nodoc:
|
12
15
|
def self.new(app, constraints, request = Rack::Request)
|
13
16
|
if constraints.any?
|
@@ -26,15 +29,10 @@ module ActionDispatch
|
|
26
29
|
def matches?(env)
|
27
30
|
req = @request.new(env)
|
28
31
|
|
29
|
-
@constraints.
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
return false
|
34
|
-
end
|
35
|
-
}
|
36
|
-
|
37
|
-
return true
|
32
|
+
@constraints.all? do |constraint|
|
33
|
+
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
|
34
|
+
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
|
35
|
+
end
|
38
36
|
ensure
|
39
37
|
req.reset_parameters
|
40
38
|
end
|
@@ -50,100 +48,159 @@ module ActionDispatch
|
|
50
48
|
end
|
51
49
|
|
52
50
|
class Mapping #:nodoc:
|
53
|
-
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
|
51
|
+
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
|
54
52
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
55
53
|
WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
|
56
54
|
|
55
|
+
attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
|
56
|
+
|
57
57
|
def initialize(set, scope, path, options)
|
58
|
-
@set, @scope = set, scope
|
59
|
-
@
|
60
|
-
|
58
|
+
@set, @scope, @path, @options = set, scope, path, options
|
59
|
+
@requirements, @conditions, @defaults = {}, {}, {}
|
60
|
+
|
61
|
+
normalize_path!
|
61
62
|
normalize_options!
|
63
|
+
normalize_requirements!
|
64
|
+
normalize_conditions!
|
65
|
+
normalize_defaults!
|
62
66
|
end
|
63
67
|
|
64
68
|
def to_route
|
65
|
-
[ app, conditions, requirements, defaults,
|
69
|
+
[ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
|
66
70
|
end
|
67
71
|
|
68
72
|
private
|
69
73
|
|
74
|
+
def normalize_path!
|
75
|
+
raise ArgumentError, "path is required" if @path.blank?
|
76
|
+
@path = Mapper.normalize_path(@path)
|
77
|
+
|
78
|
+
if required_format?
|
79
|
+
@path = "#{@path}.:format"
|
80
|
+
elsif optional_format?
|
81
|
+
@path = "#{@path}(.:format)"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def required_format?
|
86
|
+
options[:format] == true
|
87
|
+
end
|
88
|
+
|
89
|
+
def optional_format?
|
90
|
+
options[:format] != false && !path.include?(':format') && !path.end_with?('/')
|
91
|
+
end
|
92
|
+
|
70
93
|
def normalize_options!
|
94
|
+
@options.reverse_merge!(scope[:options]) if scope[:options]
|
95
|
+
path_without_format = path.sub(/\(\.:format\)$/, '')
|
96
|
+
|
97
|
+
# Add a constraint for wildcard route to make it non-greedy and match the
|
98
|
+
# optional format part of the route by default
|
99
|
+
if path_without_format.match(WILDCARD_PATH) && @options[:format] != false
|
100
|
+
@options[$1.to_sym] ||= /.+?/
|
101
|
+
end
|
102
|
+
|
103
|
+
if path_without_format.match(':controller')
|
104
|
+
raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
|
105
|
+
|
106
|
+
# Add a default constraint for :controller path segments that matches namespaced
|
107
|
+
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
108
|
+
# GET /admin/products/show/1
|
109
|
+
# => { controller: 'admin/products', action: 'show', id: '1' }
|
110
|
+
@options[:controller] ||= /.+?/
|
111
|
+
end
|
112
|
+
|
71
113
|
@options.merge!(default_controller_and_action)
|
114
|
+
end
|
72
115
|
|
73
|
-
|
74
|
-
|
75
|
-
|
116
|
+
def normalize_format!
|
117
|
+
if options[:format] == true
|
118
|
+
options[:format] = /.+/
|
119
|
+
elsif options[:format] == false
|
120
|
+
options.delete(:format)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def normalize_requirements!
|
125
|
+
constraints.each do |key, requirement|
|
126
|
+
next unless segment_keys.include?(key) || key == :controller
|
76
127
|
|
77
128
|
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
78
129
|
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
79
130
|
end
|
131
|
+
|
80
132
|
if requirement.multiline?
|
81
|
-
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
|
133
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
82
134
|
end
|
135
|
+
|
136
|
+
@requirements[key] = requirement
|
83
137
|
end
|
84
|
-
end
|
85
138
|
|
86
|
-
|
87
|
-
|
88
|
-
|
139
|
+
if options[:format] == true
|
140
|
+
@requirements[:format] = /.+/
|
141
|
+
elsif Regexp === options[:format]
|
142
|
+
@requirements[:format] = options[:format]
|
143
|
+
elsif String === options[:format]
|
144
|
+
@requirements[:format] = Regexp.compile(options[:format])
|
145
|
+
end
|
89
146
|
end
|
90
147
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
if path.match(':controller')
|
96
|
-
raise ArgumentError, ":controller segment is not allowed within a namespace block" if @scope[:module]
|
148
|
+
def normalize_defaults!
|
149
|
+
@defaults.merge!(scope[:defaults]) if scope[:defaults]
|
150
|
+
@defaults.merge!(options[:defaults]) if options[:defaults]
|
97
151
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
# => { :controller => 'admin/products', :action => 'show', :id => '1' }
|
102
|
-
@options[:controller] ||= /.+?/
|
152
|
+
options.each do |key, default|
|
153
|
+
next if Regexp === default || IGNORE_OPTIONS.include?(key)
|
154
|
+
@defaults[key] = default
|
103
155
|
end
|
104
156
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
157
|
+
if options[:constraints].is_a?(Hash)
|
158
|
+
options[:constraints].each do |key, default|
|
159
|
+
next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
|
160
|
+
@defaults[key] ||= default
|
161
|
+
end
|
109
162
|
end
|
110
163
|
|
111
|
-
if
|
112
|
-
@
|
113
|
-
|
114
|
-
|
115
|
-
path
|
116
|
-
elsif @options[:format] == true
|
117
|
-
"#{path}.:format"
|
118
|
-
else
|
119
|
-
"#{path}(.:format)"
|
164
|
+
if Regexp === options[:format]
|
165
|
+
@defaults[:format] = nil
|
166
|
+
elsif String === options[:format]
|
167
|
+
@defaults[:format] = options[:format]
|
120
168
|
end
|
121
169
|
end
|
122
170
|
|
123
|
-
def
|
124
|
-
|
125
|
-
to.respond_to?(:call) ? to : Routing::RouteSet::Dispatcher.new(:defaults => defaults),
|
126
|
-
blocks,
|
127
|
-
@set.request_class
|
128
|
-
)
|
129
|
-
end
|
171
|
+
def normalize_conditions!
|
172
|
+
@conditions.merge!(:path_info => path)
|
130
173
|
|
131
|
-
|
132
|
-
|
133
|
-
|
174
|
+
constraints.each do |key, condition|
|
175
|
+
next if segment_keys.include?(key) || key == :controller
|
176
|
+
@conditions[key] = condition
|
177
|
+
end
|
134
178
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
179
|
+
@conditions[:required_defaults] = []
|
180
|
+
options.each do |key, required_default|
|
181
|
+
next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key)
|
182
|
+
next if Regexp === required_default
|
183
|
+
@conditions[:required_defaults] << key
|
139
184
|
end
|
140
|
-
end
|
141
185
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
186
|
+
via_all = options.delete(:via) if options[:via] == :all
|
187
|
+
|
188
|
+
if !via_all && options[:via].blank?
|
189
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
190
|
+
"If you want to expose your action to GET, use `get` in the router:\n\n" \
|
191
|
+
" Instead of: match \"controller#action\"\n" \
|
192
|
+
" Do: get \"controller#action\""
|
193
|
+
raise msg
|
146
194
|
end
|
195
|
+
|
196
|
+
if via = options[:via]
|
197
|
+
list = Array(via).map { |m| m.to_s.dasherize.upcase }
|
198
|
+
@conditions.merge!(:request_method => list)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def app
|
203
|
+
Constraints.new(endpoint, blocks, @set.request_class)
|
147
204
|
end
|
148
205
|
|
149
206
|
def default_controller_and_action
|
@@ -170,14 +227,20 @@ module ActionDispatch
|
|
170
227
|
controller = controller.to_s unless controller.is_a?(Regexp)
|
171
228
|
action = action.to_s unless action.is_a?(Regexp)
|
172
229
|
|
173
|
-
if controller.blank? && segment_keys.exclude?(
|
230
|
+
if controller.blank? && segment_keys.exclude?(:controller)
|
174
231
|
raise ArgumentError, "missing :controller"
|
175
232
|
end
|
176
233
|
|
177
|
-
if action.blank? && segment_keys.exclude?(
|
234
|
+
if action.blank? && segment_keys.exclude?(:action)
|
178
235
|
raise ArgumentError, "missing :action"
|
179
236
|
end
|
180
237
|
|
238
|
+
if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
|
239
|
+
message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems."
|
240
|
+
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
241
|
+
raise ArgumentError, message
|
242
|
+
end
|
243
|
+
|
181
244
|
hash = {}
|
182
245
|
hash[:controller] = controller unless controller.blank?
|
183
246
|
hash[:action] = action unless action.blank?
|
@@ -186,43 +249,55 @@ module ActionDispatch
|
|
186
249
|
end
|
187
250
|
|
188
251
|
def blocks
|
189
|
-
constraints
|
190
|
-
|
191
|
-
[constraints]
|
252
|
+
if options[:constraints].present? && !options[:constraints].is_a?(Hash)
|
253
|
+
[options[:constraints]]
|
192
254
|
else
|
193
|
-
|
255
|
+
scope[:blocks] || []
|
194
256
|
end
|
195
257
|
end
|
196
258
|
|
197
259
|
def constraints
|
198
|
-
@constraints ||=
|
199
|
-
|
260
|
+
@constraints ||= {}.tap do |constraints|
|
261
|
+
constraints.merge!(scope[:constraints]) if scope[:constraints]
|
200
262
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
{ }
|
263
|
+
options.except(*IGNORE_OPTIONS).each do |key, option|
|
264
|
+
constraints[key] = option if Regexp === option
|
265
|
+
end
|
266
|
+
|
267
|
+
constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
|
207
268
|
end
|
208
269
|
end
|
209
270
|
|
210
271
|
def segment_keys
|
211
|
-
@segment_keys ||=
|
212
|
-
|
213
|
-
|
272
|
+
@segment_keys ||= path_pattern.names.map{ |s| s.to_sym }
|
273
|
+
end
|
274
|
+
|
275
|
+
def path_pattern
|
276
|
+
Journey::Path::Pattern.new(strexp)
|
277
|
+
end
|
278
|
+
|
279
|
+
def strexp
|
280
|
+
Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
|
281
|
+
end
|
282
|
+
|
283
|
+
def endpoint
|
284
|
+
to.respond_to?(:call) ? to : dispatcher
|
285
|
+
end
|
286
|
+
|
287
|
+
def dispatcher
|
288
|
+
Routing::RouteSet::Dispatcher.new(:defaults => defaults)
|
214
289
|
end
|
215
290
|
|
216
291
|
def to
|
217
|
-
|
292
|
+
options[:to]
|
218
293
|
end
|
219
294
|
|
220
295
|
def default_controller
|
221
|
-
|
296
|
+
options[:controller] || scope[:controller]
|
222
297
|
end
|
223
298
|
|
224
299
|
def default_action
|
225
|
-
|
300
|
+
options[:action] || scope[:action]
|
226
301
|
end
|
227
302
|
end
|
228
303
|
|
@@ -236,21 +311,26 @@ module ActionDispatch
|
|
236
311
|
end
|
237
312
|
|
238
313
|
def self.normalize_name(name)
|
239
|
-
normalize_path(name)[1..-1].
|
314
|
+
normalize_path(name)[1..-1].tr("/", "_")
|
240
315
|
end
|
241
316
|
|
242
317
|
module Base
|
243
318
|
# You can specify what Rails should route "/" to with the root method:
|
244
319
|
#
|
245
|
-
# root :
|
320
|
+
# root to: 'pages#main'
|
246
321
|
#
|
247
322
|
# For options, see +match+, as +root+ uses it internally.
|
248
323
|
#
|
324
|
+
# You can also pass a string which will expand
|
325
|
+
#
|
326
|
+
# root 'pages#main'
|
327
|
+
#
|
249
328
|
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
250
329
|
# because this means it will be matched first. As this is the most popular route
|
251
330
|
# of most Rails applications, this is beneficial.
|
252
331
|
def root(options = {})
|
253
|
-
|
332
|
+
options = { :to => options } if options.is_a?(String)
|
333
|
+
match '/', { :as => :root, :via => :get }.merge!(options)
|
254
334
|
end
|
255
335
|
|
256
336
|
# Matches a url pattern to one or more routes. Any symbols in a pattern
|
@@ -264,7 +344,7 @@ module ActionDispatch
|
|
264
344
|
# and +:action+ to the controller's action. A pattern can also map
|
265
345
|
# wildcard segments (globs) to params:
|
266
346
|
#
|
267
|
-
# match 'songs/*category/:title'
|
347
|
+
# match 'songs/*category/:title', to: 'songs#show'
|
268
348
|
#
|
269
349
|
# # 'songs/rock/classic/stairway-to-heaven' sets
|
270
350
|
# # params[:category] = 'rock/classic'
|
@@ -274,16 +354,20 @@ module ActionDispatch
|
|
274
354
|
# +:controller+ should be set in options or hash shorthand. Examples:
|
275
355
|
#
|
276
356
|
# match 'photos/:id' => 'photos#show'
|
277
|
-
# match 'photos/:id', :
|
278
|
-
# match 'photos/:id', :
|
357
|
+
# match 'photos/:id', to: 'photos#show'
|
358
|
+
# match 'photos/:id', controller: 'photos', action: 'show'
|
279
359
|
#
|
280
360
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
281
361
|
# responds to +call+:
|
282
362
|
#
|
283
|
-
# match 'photos/:id'
|
284
|
-
# match 'photos/:id'
|
363
|
+
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }
|
364
|
+
# match 'photos/:id', to: PhotoRackApp
|
285
365
|
# # Yes, controller actions are just rack endpoints
|
286
|
-
# match 'photos/:id'
|
366
|
+
# match 'photos/:id', to: PhotosController.action(:show)
|
367
|
+
#
|
368
|
+
# Because request various HTTP verbs with a single action has security
|
369
|
+
# implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers]
|
370
|
+
# instead +match+
|
287
371
|
#
|
288
372
|
# === Options
|
289
373
|
#
|
@@ -301,7 +385,7 @@ module ActionDispatch
|
|
301
385
|
# [:module]
|
302
386
|
# The namespace for :controller.
|
303
387
|
#
|
304
|
-
# match 'path'
|
388
|
+
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
|
305
389
|
# #=> Sekret::PostsController
|
306
390
|
#
|
307
391
|
# See <tt>Scoping#namespace</tt> for its scope equivalent.
|
@@ -312,16 +396,17 @@ module ActionDispatch
|
|
312
396
|
# [:via]
|
313
397
|
# Allowed HTTP verb(s) for route.
|
314
398
|
#
|
315
|
-
# match 'path'
|
316
|
-
# match 'path'
|
399
|
+
# match 'path', to: 'c#a', via: :get
|
400
|
+
# match 'path', to: 'c#a', via: [:get, :post]
|
401
|
+
# match 'path', to: 'c#a', via: :all
|
317
402
|
#
|
318
403
|
# [:to]
|
319
404
|
# Points to a +Rack+ endpoint. Can be an object that responds to
|
320
405
|
# +call+ or a string representing a controller's action.
|
321
406
|
#
|
322
|
-
# match 'path', :
|
323
|
-
# match 'path', :
|
324
|
-
# match 'path', :
|
407
|
+
# match 'path', to: 'controller#action'
|
408
|
+
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }
|
409
|
+
# match 'path', to: RackApp
|
325
410
|
#
|
326
411
|
# [:on]
|
327
412
|
# Shorthand for wrapping routes in a specific RESTful context. Valid
|
@@ -329,14 +414,14 @@ module ActionDispatch
|
|
329
414
|
# <tt>resource(s)</tt> block. For example:
|
330
415
|
#
|
331
416
|
# resource :bar do
|
332
|
-
# match 'foo'
|
417
|
+
# match 'foo', to: 'c#a', on: :member, via: [:get, :post]
|
333
418
|
# end
|
334
419
|
#
|
335
420
|
# Is equivalent to:
|
336
421
|
#
|
337
422
|
# resource :bar do
|
338
423
|
# member do
|
339
|
-
# match 'foo'
|
424
|
+
# match 'foo', to: 'c#a', via: [:get, :post]
|
340
425
|
# end
|
341
426
|
# end
|
342
427
|
#
|
@@ -344,12 +429,12 @@ module ActionDispatch
|
|
344
429
|
# Constrains parameters with a hash of regular expressions or an
|
345
430
|
# object that responds to <tt>matches?</tt>
|
346
431
|
#
|
347
|
-
# match 'path/:id', :
|
432
|
+
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
|
348
433
|
#
|
349
434
|
# class Blacklist
|
350
435
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
351
436
|
# end
|
352
|
-
# match 'path'
|
437
|
+
# match 'path', to: 'c#a', constraints: Blacklist.new
|
353
438
|
#
|
354
439
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
355
440
|
# equivalent.
|
@@ -358,7 +443,7 @@ module ActionDispatch
|
|
358
443
|
# Sets defaults for parameters
|
359
444
|
#
|
360
445
|
# # Sets params[:format] to 'jpg' by default
|
361
|
-
# match 'path'
|
446
|
+
# match 'path', to: 'c#a', defaults: { format: 'jpg' }
|
362
447
|
#
|
363
448
|
# See <tt>Scoping#defaults</tt> for its scope equivalent.
|
364
449
|
#
|
@@ -367,13 +452,17 @@ module ActionDispatch
|
|
367
452
|
# false, the pattern matches any request prefixed with the given path.
|
368
453
|
#
|
369
454
|
# # Matches any request starting with 'path'
|
370
|
-
# match 'path'
|
455
|
+
# match 'path', to: 'c#a', anchor: false
|
456
|
+
#
|
457
|
+
# [:format]
|
458
|
+
# Allows you to specify the default value for optional +format+
|
459
|
+
# segment or disable it by supplying +false+.
|
371
460
|
def match(path, options=nil)
|
372
461
|
end
|
373
462
|
|
374
463
|
# Mount a Rack-based application to be used within the application.
|
375
464
|
#
|
376
|
-
# mount SomeRackApp, :
|
465
|
+
# mount SomeRackApp, at: "some_route"
|
377
466
|
#
|
378
467
|
# Alternatively:
|
379
468
|
#
|
@@ -386,7 +475,7 @@ module ActionDispatch
|
|
386
475
|
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
|
387
476
|
# To customize this helper's name, use the +:as+ option:
|
388
477
|
#
|
389
|
-
# mount(SomeRackApp => "some_route", :
|
478
|
+
# mount(SomeRackApp => "some_route", as: "exciting")
|
390
479
|
#
|
391
480
|
# This will generate the +exciting_path+ and +exciting_url+ helpers
|
392
481
|
# which can be used to navigate to this mounted app.
|
@@ -394,6 +483,10 @@ module ActionDispatch
|
|
394
483
|
if options
|
395
484
|
path = options.delete(:at)
|
396
485
|
else
|
486
|
+
unless Hash === app
|
487
|
+
raise ArgumentError, "must be called with mount point"
|
488
|
+
end
|
489
|
+
|
397
490
|
options = app
|
398
491
|
app, path = options.find { |k, v| k.respond_to?(:call) }
|
399
492
|
options.delete(app) if app
|
@@ -401,7 +494,8 @@ module ActionDispatch
|
|
401
494
|
|
402
495
|
raise "A rack application must be specified" unless path
|
403
496
|
|
404
|
-
options[:as]
|
497
|
+
options[:as] ||= app_name(app)
|
498
|
+
options[:via] ||= :all
|
405
499
|
|
406
500
|
match(path, options.merge(:to => app, :anchor => false, :format => false))
|
407
501
|
|
@@ -428,7 +522,7 @@ module ActionDispatch
|
|
428
522
|
app.railtie_name
|
429
523
|
else
|
430
524
|
class_name = app.class.is_a?(Class) ? app.name : app.class.name
|
431
|
-
ActiveSupport::Inflector.underscore(class_name).
|
525
|
+
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
|
432
526
|
end
|
433
527
|
end
|
434
528
|
|
@@ -438,14 +532,16 @@ module ActionDispatch
|
|
438
532
|
_route = @set.named_routes.routes[name.to_sym]
|
439
533
|
_routes = @set
|
440
534
|
app.routes.define_mounted_helper(name)
|
441
|
-
app.routes.class_eval do
|
535
|
+
app.routes.singleton_class.class_eval do
|
536
|
+
define_method :mounted? do
|
537
|
+
true
|
538
|
+
end
|
539
|
+
|
442
540
|
define_method :_generate_prefix do |options|
|
443
541
|
prefix_options = options.slice(*_route.segment_keys)
|
444
542
|
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
445
543
|
_route.segment_keys.each { |k| options.delete(k) }
|
446
|
-
|
447
|
-
prefix = prefix.gsub(%r{/\z}, '')
|
448
|
-
prefix
|
544
|
+
_routes.url_helpers.send("#{name}_path", prefix_options)
|
449
545
|
end
|
450
546
|
end
|
451
547
|
end
|
@@ -453,51 +549,51 @@ module ActionDispatch
|
|
453
549
|
|
454
550
|
module HttpHelpers
|
455
551
|
# Define a route that only recognizes HTTP GET.
|
456
|
-
# For supported arguments, see
|
457
|
-
#
|
458
|
-
# Example:
|
552
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
459
553
|
#
|
460
|
-
#
|
554
|
+
# get 'bacon', to: 'food#bacon'
|
461
555
|
def get(*args, &block)
|
462
|
-
map_method(:get,
|
556
|
+
map_method(:get, args, &block)
|
463
557
|
end
|
464
558
|
|
465
559
|
# Define a route that only recognizes HTTP POST.
|
466
|
-
# For supported arguments, see
|
467
|
-
#
|
468
|
-
# Example:
|
560
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
469
561
|
#
|
470
|
-
#
|
562
|
+
# post 'bacon', to: 'food#bacon'
|
471
563
|
def post(*args, &block)
|
472
|
-
map_method(:post,
|
564
|
+
map_method(:post, args, &block)
|
473
565
|
end
|
474
566
|
|
475
|
-
# Define a route that only recognizes HTTP
|
476
|
-
# For supported arguments, see
|
477
|
-
#
|
478
|
-
# Example:
|
567
|
+
# Define a route that only recognizes HTTP PATCH.
|
568
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
479
569
|
#
|
480
|
-
#
|
481
|
-
def
|
482
|
-
map_method(:
|
570
|
+
# patch 'bacon', to: 'food#bacon'
|
571
|
+
def patch(*args, &block)
|
572
|
+
map_method(:patch, args, &block)
|
483
573
|
end
|
484
574
|
|
485
575
|
# Define a route that only recognizes HTTP PUT.
|
486
|
-
# For supported arguments, see
|
576
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
487
577
|
#
|
488
|
-
#
|
578
|
+
# put 'bacon', to: 'food#bacon'
|
579
|
+
def put(*args, &block)
|
580
|
+
map_method(:put, args, &block)
|
581
|
+
end
|
582
|
+
|
583
|
+
# Define a route that only recognizes HTTP DELETE.
|
584
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
489
585
|
#
|
490
|
-
#
|
586
|
+
# delete 'broccoli', to: 'food#broccoli'
|
491
587
|
def delete(*args, &block)
|
492
|
-
map_method(:delete,
|
588
|
+
map_method(:delete, args, &block)
|
493
589
|
end
|
494
590
|
|
495
591
|
private
|
496
|
-
def map_method(method,
|
592
|
+
def map_method(method, args, &block)
|
497
593
|
options = args.extract_options!
|
498
|
-
options[:via]
|
499
|
-
args.
|
500
|
-
match(*args, &block)
|
594
|
+
options[:via] = method
|
595
|
+
options[:path] ||= args.first if args.first.is_a?(String)
|
596
|
+
match(*args, options, &block)
|
501
597
|
self
|
502
598
|
end
|
503
599
|
end
|
@@ -515,24 +611,24 @@ module ActionDispatch
|
|
515
611
|
# This will create a number of routes for each of the posts and comments
|
516
612
|
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
|
517
613
|
#
|
518
|
-
# GET
|
519
|
-
# GET
|
520
|
-
# POST
|
521
|
-
# GET
|
522
|
-
# GET
|
523
|
-
# PUT
|
524
|
-
# DELETE
|
614
|
+
# GET /admin/posts
|
615
|
+
# GET /admin/posts/new
|
616
|
+
# POST /admin/posts
|
617
|
+
# GET /admin/posts/1
|
618
|
+
# GET /admin/posts/1/edit
|
619
|
+
# PATCH/PUT /admin/posts/1
|
620
|
+
# DELETE /admin/posts/1
|
525
621
|
#
|
526
622
|
# If you want to route /posts (without the prefix /admin) to
|
527
623
|
# <tt>Admin::PostsController</tt>, you could use
|
528
624
|
#
|
529
|
-
# scope :
|
625
|
+
# scope module: "admin" do
|
530
626
|
# resources :posts
|
531
627
|
# end
|
532
628
|
#
|
533
629
|
# or, for a single case
|
534
630
|
#
|
535
|
-
# resources :posts, :
|
631
|
+
# resources :posts, module: "admin"
|
536
632
|
#
|
537
633
|
# If you want to route /admin/posts to +PostsController+
|
538
634
|
# (without the Admin:: module prefix), you could use
|
@@ -543,25 +639,25 @@ module ActionDispatch
|
|
543
639
|
#
|
544
640
|
# or, for a single case
|
545
641
|
#
|
546
|
-
# resources :posts, :
|
642
|
+
# resources :posts, path: "/admin/posts"
|
547
643
|
#
|
548
644
|
# In each of these cases, the named routes remain the same as if you did
|
549
645
|
# not use scope. In the last case, the following paths map to
|
550
646
|
# +PostsController+:
|
551
647
|
#
|
552
|
-
# GET
|
553
|
-
# GET
|
554
|
-
# POST
|
555
|
-
# GET
|
556
|
-
# GET
|
557
|
-
# PUT
|
558
|
-
# DELETE
|
648
|
+
# GET /admin/posts
|
649
|
+
# GET /admin/posts/new
|
650
|
+
# POST /admin/posts
|
651
|
+
# GET /admin/posts/1
|
652
|
+
# GET /admin/posts/1/edit
|
653
|
+
# PATCH/PUT /admin/posts/1
|
654
|
+
# DELETE /admin/posts/1
|
559
655
|
module Scoping
|
560
656
|
# Scopes a set of routes to the given default options.
|
561
657
|
#
|
562
658
|
# Take the following route definition as an example:
|
563
659
|
#
|
564
|
-
# scope :
|
660
|
+
# scope path: ":account_id", as: "account" do
|
565
661
|
# resources :projects
|
566
662
|
# end
|
567
663
|
#
|
@@ -573,31 +669,34 @@ module ActionDispatch
|
|
573
669
|
#
|
574
670
|
# Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
|
575
671
|
#
|
576
|
-
# === Examples
|
577
|
-
#
|
578
672
|
# # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
|
579
|
-
# scope :
|
673
|
+
# scope module: "admin" do
|
580
674
|
# resources :posts
|
581
675
|
# end
|
582
676
|
#
|
583
677
|
# # prefix the posts resource's requests with '/admin'
|
584
|
-
# scope :
|
678
|
+
# scope path: "/admin" do
|
585
679
|
# resources :posts
|
586
680
|
# end
|
587
681
|
#
|
588
682
|
# # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
|
589
|
-
# scope :
|
683
|
+
# scope as: "sekret" do
|
590
684
|
# resources :posts
|
591
685
|
# end
|
592
686
|
def scope(*args)
|
593
|
-
options = args.extract_options
|
594
|
-
options = options.dup
|
595
|
-
|
596
|
-
options[:path] = args.first if args.first.is_a?(String)
|
687
|
+
options = args.extract_options!.dup
|
597
688
|
recover = {}
|
598
689
|
|
690
|
+
options[:path] = args.flatten.join('/') if args.any?
|
599
691
|
options[:constraints] ||= {}
|
600
|
-
|
692
|
+
|
693
|
+
if options[:constraints].is_a?(Hash)
|
694
|
+
defaults = options[:constraints].select do
|
695
|
+
|k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
|
696
|
+
end
|
697
|
+
|
698
|
+
(options[:defaults] ||= {}).reverse_merge!(defaults)
|
699
|
+
else
|
601
700
|
block, options[:constraints] = options[:constraints], {}
|
602
701
|
end
|
603
702
|
|
@@ -608,8 +707,8 @@ module ActionDispatch
|
|
608
707
|
end
|
609
708
|
end
|
610
709
|
|
611
|
-
recover[:
|
612
|
-
@scope[:blocks]
|
710
|
+
recover[:blocks] = @scope[:blocks]
|
711
|
+
@scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
|
613
712
|
|
614
713
|
recover[:options] = @scope[:options]
|
615
714
|
@scope[:options] = merge_options_scope(@scope[:options], options)
|
@@ -617,19 +716,13 @@ module ActionDispatch
|
|
617
716
|
yield
|
618
717
|
self
|
619
718
|
ensure
|
620
|
-
|
621
|
-
@scope[option] = recover[option] if recover.has_key?(option)
|
622
|
-
end
|
623
|
-
|
624
|
-
@scope[:options] = recover[:options]
|
625
|
-
@scope[:blocks] = recover[:block]
|
719
|
+
@scope.merge!(recover)
|
626
720
|
end
|
627
721
|
|
628
722
|
# Scopes routes to a specific controller
|
629
723
|
#
|
630
|
-
# Example:
|
631
724
|
# controller "food" do
|
632
|
-
# match "bacon", :
|
725
|
+
# match "bacon", action: "bacon"
|
633
726
|
# end
|
634
727
|
def controller(controller, options={})
|
635
728
|
options[:controller] = controller
|
@@ -644,13 +737,13 @@ module ActionDispatch
|
|
644
737
|
#
|
645
738
|
# This generates the following routes:
|
646
739
|
#
|
647
|
-
# admin_posts GET
|
648
|
-
# admin_posts POST
|
649
|
-
# new_admin_post GET
|
650
|
-
# edit_admin_post GET
|
651
|
-
# admin_post GET
|
652
|
-
# admin_post PUT
|
653
|
-
# admin_post DELETE
|
740
|
+
# admin_posts GET /admin/posts(.:format) admin/posts#index
|
741
|
+
# admin_posts POST /admin/posts(.:format) admin/posts#create
|
742
|
+
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
|
743
|
+
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
|
744
|
+
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
|
745
|
+
# admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
|
746
|
+
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
|
654
747
|
#
|
655
748
|
# === Options
|
656
749
|
#
|
@@ -660,20 +753,18 @@ module ActionDispatch
|
|
660
753
|
# For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
|
661
754
|
# <tt>Resources#resources</tt>.
|
662
755
|
#
|
663
|
-
# === Examples
|
664
|
-
#
|
665
756
|
# # accessible through /sekret/posts rather than /admin/posts
|
666
|
-
# namespace :admin, :
|
757
|
+
# namespace :admin, path: "sekret" do
|
667
758
|
# resources :posts
|
668
759
|
# end
|
669
760
|
#
|
670
761
|
# # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
|
671
|
-
# namespace :admin, :
|
762
|
+
# namespace :admin, module: "sekret" do
|
672
763
|
# resources :posts
|
673
764
|
# end
|
674
765
|
#
|
675
766
|
# # generates +sekret_posts_path+ rather than +admin_posts_path+
|
676
|
-
# namespace :admin, :
|
767
|
+
# namespace :admin, as: "sekret" do
|
677
768
|
# resources :posts
|
678
769
|
# end
|
679
770
|
def namespace(path, options = {})
|
@@ -687,7 +778,7 @@ module ActionDispatch
|
|
687
778
|
# Allows you to constrain the nested routes based on a set of rules.
|
688
779
|
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
|
689
780
|
#
|
690
|
-
# constraints(:
|
781
|
+
# constraints(id: /\d+\.\d+/) do
|
691
782
|
# resources :posts
|
692
783
|
# end
|
693
784
|
#
|
@@ -697,7 +788,7 @@ module ActionDispatch
|
|
697
788
|
# You may use this to also restrict other parameters:
|
698
789
|
#
|
699
790
|
# resources :posts do
|
700
|
-
# constraints(:
|
791
|
+
# constraints(post_id: /\d+\.\d+/) do
|
701
792
|
# resources :comments
|
702
793
|
# end
|
703
794
|
# end
|
@@ -706,7 +797,7 @@ module ActionDispatch
|
|
706
797
|
#
|
707
798
|
# Routes can also be constrained to an IP or a certain range of IP addresses:
|
708
799
|
#
|
709
|
-
# constraints(:
|
800
|
+
# constraints(ip: /192\.168\.\d+\.\d+/) do
|
710
801
|
# resources :posts
|
711
802
|
# end
|
712
803
|
#
|
@@ -743,8 +834,8 @@ module ActionDispatch
|
|
743
834
|
end
|
744
835
|
|
745
836
|
# Allows you to set default parameters for a route, such as this:
|
746
|
-
# defaults :
|
747
|
-
# match 'scoped_pages/(:id)', :
|
837
|
+
# defaults id: 'home' do
|
838
|
+
# match 'scoped_pages/(:id)', to: 'pages#show'
|
748
839
|
# end
|
749
840
|
# Using this, the +:id+ parameter here will default to 'home'.
|
750
841
|
def defaults(defaults = {})
|
@@ -780,10 +871,6 @@ module ActionDispatch
|
|
780
871
|
child
|
781
872
|
end
|
782
873
|
|
783
|
-
def merge_action_scope(parent, child) #:nodoc:
|
784
|
-
child
|
785
|
-
end
|
786
|
-
|
787
874
|
def merge_path_names_scope(parent, child) #:nodoc:
|
788
875
|
merge_options_scope(parent, child)
|
789
876
|
end
|
@@ -803,7 +890,7 @@ module ActionDispatch
|
|
803
890
|
end
|
804
891
|
|
805
892
|
def merge_options_scope(parent, child) #:nodoc:
|
806
|
-
(parent || {}).except(*override_keys(child)).merge(child)
|
893
|
+
(parent || {}).except(*override_keys(child)).merge!(child)
|
807
894
|
end
|
808
895
|
|
809
896
|
def merge_shallow_scope(parent, child) #:nodoc:
|
@@ -850,7 +937,7 @@ module ActionDispatch
|
|
850
937
|
# use dots as part of the +:id+ parameter add a constraint which
|
851
938
|
# overrides this restriction, e.g:
|
852
939
|
#
|
853
|
-
# resources :articles, :
|
940
|
+
# resources :articles, id: /[^\/]+/
|
854
941
|
#
|
855
942
|
# This allows any character other than a slash as part of your +:id+.
|
856
943
|
#
|
@@ -858,17 +945,18 @@ module ActionDispatch
|
|
858
945
|
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
|
859
946
|
# a path appended since they fit properly in their scope level.
|
860
947
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
861
|
-
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
|
948
|
+
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
862
949
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
863
950
|
|
864
951
|
class Resource #:nodoc:
|
865
|
-
attr_reader :controller, :path, :options
|
952
|
+
attr_reader :controller, :path, :options, :param
|
866
953
|
|
867
954
|
def initialize(entities, options = {})
|
868
955
|
@name = entities.to_s
|
869
956
|
@path = (options[:path] || @name).to_s
|
870
957
|
@controller = (options[:controller] || @name).to_s
|
871
958
|
@as = options[:as]
|
959
|
+
@param = (options[:param] || :id).to_sym
|
872
960
|
@options = options
|
873
961
|
end
|
874
962
|
|
@@ -913,15 +1001,21 @@ module ActionDispatch
|
|
913
1001
|
alias :collection_scope :path
|
914
1002
|
|
915
1003
|
def member_scope
|
916
|
-
"#{path}
|
1004
|
+
"#{path}/:#{param}"
|
917
1005
|
end
|
918
1006
|
|
1007
|
+
alias :shallow_scope :member_scope
|
1008
|
+
|
919
1009
|
def new_scope(new_path)
|
920
1010
|
"#{path}/#{new_path}"
|
921
1011
|
end
|
922
1012
|
|
1013
|
+
def nested_param
|
1014
|
+
:"#{singular}_#{param}"
|
1015
|
+
end
|
1016
|
+
|
923
1017
|
def nested_scope
|
924
|
-
"#{path}/:#{
|
1018
|
+
"#{path}/:#{nested_param}"
|
925
1019
|
end
|
926
1020
|
|
927
1021
|
end
|
@@ -969,12 +1063,12 @@ module ActionDispatch
|
|
969
1063
|
# the +GeoCoders+ controller (note that the controller is named after
|
970
1064
|
# the plural):
|
971
1065
|
#
|
972
|
-
# GET
|
973
|
-
# POST
|
974
|
-
# GET
|
975
|
-
# GET
|
976
|
-
# PUT
|
977
|
-
# DELETE
|
1066
|
+
# GET /geocoder/new
|
1067
|
+
# POST /geocoder
|
1068
|
+
# GET /geocoder
|
1069
|
+
# GET /geocoder/edit
|
1070
|
+
# PATCH/PUT /geocoder
|
1071
|
+
# DELETE /geocoder
|
978
1072
|
#
|
979
1073
|
# === Options
|
980
1074
|
# Takes same options as +resources+.
|
@@ -988,6 +1082,8 @@ module ActionDispatch
|
|
988
1082
|
resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
|
989
1083
|
yield if block_given?
|
990
1084
|
|
1085
|
+
concerns(options[:concerns]) if options[:concerns]
|
1086
|
+
|
991
1087
|
collection do
|
992
1088
|
post :create
|
993
1089
|
end if parent_resource.actions.include?(:create)
|
@@ -996,12 +1092,7 @@ module ActionDispatch
|
|
996
1092
|
get :new
|
997
1093
|
end if parent_resource.actions.include?(:new)
|
998
1094
|
|
999
|
-
|
1000
|
-
get :edit if parent_resource.actions.include?(:edit)
|
1001
|
-
get :show if parent_resource.actions.include?(:show)
|
1002
|
-
put :update if parent_resource.actions.include?(:update)
|
1003
|
-
delete :destroy if parent_resource.actions.include?(:destroy)
|
1004
|
-
end
|
1095
|
+
set_member_mappings_for_resource
|
1005
1096
|
end
|
1006
1097
|
|
1007
1098
|
self
|
@@ -1017,13 +1108,13 @@ module ActionDispatch
|
|
1017
1108
|
# creates seven different routes in your application, all mapping to
|
1018
1109
|
# the +Photos+ controller:
|
1019
1110
|
#
|
1020
|
-
# GET
|
1021
|
-
# GET
|
1022
|
-
# POST
|
1023
|
-
# GET
|
1024
|
-
# GET
|
1025
|
-
# PUT
|
1026
|
-
# DELETE
|
1111
|
+
# GET /photos
|
1112
|
+
# GET /photos/new
|
1113
|
+
# POST /photos
|
1114
|
+
# GET /photos/:id
|
1115
|
+
# GET /photos/:id/edit
|
1116
|
+
# PATCH/PUT /photos/:id
|
1117
|
+
# DELETE /photos/:id
|
1027
1118
|
#
|
1028
1119
|
# Resources can also be nested infinitely by using this block syntax:
|
1029
1120
|
#
|
@@ -1033,13 +1124,13 @@ module ActionDispatch
|
|
1033
1124
|
#
|
1034
1125
|
# This generates the following comments routes:
|
1035
1126
|
#
|
1036
|
-
# GET
|
1037
|
-
# GET
|
1038
|
-
# POST
|
1039
|
-
# GET
|
1040
|
-
# GET
|
1041
|
-
# PUT
|
1042
|
-
# DELETE
|
1127
|
+
# GET /photos/:photo_id/comments
|
1128
|
+
# GET /photos/:photo_id/comments/new
|
1129
|
+
# POST /photos/:photo_id/comments
|
1130
|
+
# GET /photos/:photo_id/comments/:id
|
1131
|
+
# GET /photos/:photo_id/comments/:id/edit
|
1132
|
+
# PATCH/PUT /photos/:photo_id/comments/:id
|
1133
|
+
# DELETE /photos/:photo_id/comments/:id
|
1043
1134
|
#
|
1044
1135
|
# === Options
|
1045
1136
|
# Takes same options as <tt>Base#match</tt> as well as:
|
@@ -1048,43 +1139,43 @@ module ActionDispatch
|
|
1048
1139
|
# Allows you to change the segment component of the +edit+ and +new+ actions.
|
1049
1140
|
# Actions not specified are not changed.
|
1050
1141
|
#
|
1051
|
-
# resources :posts, :
|
1142
|
+
# resources :posts, path_names: { new: "brand_new" }
|
1052
1143
|
#
|
1053
1144
|
# The above example will now change /posts/new to /posts/brand_new
|
1054
1145
|
#
|
1055
1146
|
# [:path]
|
1056
1147
|
# Allows you to change the path prefix for the resource.
|
1057
1148
|
#
|
1058
|
-
# resources :posts, :
|
1149
|
+
# resources :posts, path: 'postings'
|
1059
1150
|
#
|
1060
1151
|
# The resource and all segments will now route to /postings instead of /posts
|
1061
1152
|
#
|
1062
1153
|
# [:only]
|
1063
1154
|
# Only generate routes for the given actions.
|
1064
1155
|
#
|
1065
|
-
# resources :cows, :
|
1066
|
-
# resources :cows, :
|
1156
|
+
# resources :cows, only: :show
|
1157
|
+
# resources :cows, only: [:show, :index]
|
1067
1158
|
#
|
1068
1159
|
# [:except]
|
1069
1160
|
# Generate all routes except for the given actions.
|
1070
1161
|
#
|
1071
|
-
# resources :cows, :
|
1072
|
-
# resources :cows, :
|
1162
|
+
# resources :cows, except: :show
|
1163
|
+
# resources :cows, except: [:show, :index]
|
1073
1164
|
#
|
1074
1165
|
# [:shallow]
|
1075
1166
|
# Generates shallow routes for nested resource(s). When placed on a parent resource,
|
1076
1167
|
# generates shallow routes for all nested resources.
|
1077
1168
|
#
|
1078
|
-
# resources :posts, :
|
1169
|
+
# resources :posts, shallow: true do
|
1079
1170
|
# resources :comments
|
1080
1171
|
# end
|
1081
1172
|
#
|
1082
1173
|
# Is the same as:
|
1083
1174
|
#
|
1084
1175
|
# resources :posts do
|
1085
|
-
# resources :comments, :
|
1176
|
+
# resources :comments, except: [:show, :edit, :update, :destroy]
|
1086
1177
|
# end
|
1087
|
-
# resources :comments, :
|
1178
|
+
# resources :comments, only: [:show, :edit, :update, :destroy]
|
1088
1179
|
#
|
1089
1180
|
# This allows URLs for resources that otherwise would be deeply nested such
|
1090
1181
|
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
|
@@ -1093,29 +1184,52 @@ module ActionDispatch
|
|
1093
1184
|
# [:shallow_path]
|
1094
1185
|
# Prefixes nested shallow routes with the specified path.
|
1095
1186
|
#
|
1096
|
-
# scope :
|
1187
|
+
# scope shallow_path: "sekret" do
|
1097
1188
|
# resources :posts do
|
1098
|
-
# resources :comments, :
|
1189
|
+
# resources :comments, shallow: true
|
1099
1190
|
# end
|
1100
1191
|
# end
|
1101
1192
|
#
|
1102
1193
|
# The +comments+ resource here will have the following routes generated for it:
|
1103
1194
|
#
|
1104
|
-
# post_comments GET
|
1105
|
-
# post_comments POST
|
1106
|
-
# new_post_comment GET
|
1107
|
-
# edit_comment GET
|
1108
|
-
# comment GET
|
1109
|
-
# comment PUT
|
1110
|
-
# comment DELETE
|
1195
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
1196
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
1197
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1198
|
+
# edit_comment GET /sekret/comments/:id/edit(.:format)
|
1199
|
+
# comment GET /sekret/comments/:id(.:format)
|
1200
|
+
# comment PATCH/PUT /sekret/comments/:id(.:format)
|
1201
|
+
# comment DELETE /sekret/comments/:id(.:format)
|
1202
|
+
#
|
1203
|
+
# [:shallow_prefix]
|
1204
|
+
# Prefixes nested shallow route names with specified prefix.
|
1205
|
+
#
|
1206
|
+
# scope shallow_prefix: "sekret" do
|
1207
|
+
# resources :posts do
|
1208
|
+
# resources :comments, shallow: true
|
1209
|
+
# end
|
1210
|
+
# end
|
1211
|
+
#
|
1212
|
+
# The +comments+ resource here will have the following routes generated for it:
|
1213
|
+
#
|
1214
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
1215
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
1216
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1217
|
+
# edit_sekret_comment GET /comments/:id/edit(.:format)
|
1218
|
+
# sekret_comment GET /comments/:id(.:format)
|
1219
|
+
# sekret_comment PATCH/PUT /comments/:id(.:format)
|
1220
|
+
# sekret_comment DELETE /comments/:id(.:format)
|
1221
|
+
#
|
1222
|
+
# [:format]
|
1223
|
+
# Allows you to specify the default value for optional +format+
|
1224
|
+
# segment or disable it by supplying +false+.
|
1111
1225
|
#
|
1112
1226
|
# === Examples
|
1113
1227
|
#
|
1114
1228
|
# # routes call <tt>Admin::PostsController</tt>
|
1115
|
-
# resources :posts, :
|
1229
|
+
# resources :posts, module: "admin"
|
1116
1230
|
#
|
1117
1231
|
# # resource actions are at /admin/posts.
|
1118
|
-
# resources :posts, :
|
1232
|
+
# resources :posts, path: "admin/posts"
|
1119
1233
|
def resources(*resources, &block)
|
1120
1234
|
options = resources.extract_options!.dup
|
1121
1235
|
|
@@ -1126,6 +1240,8 @@ module ActionDispatch
|
|
1126
1240
|
resource_scope(:resources, Resource.new(resources.pop, options)) do
|
1127
1241
|
yield if block_given?
|
1128
1242
|
|
1243
|
+
concerns(options[:concerns]) if options[:concerns]
|
1244
|
+
|
1129
1245
|
collection do
|
1130
1246
|
get :index if parent_resource.actions.include?(:index)
|
1131
1247
|
post :create if parent_resource.actions.include?(:create)
|
@@ -1135,12 +1251,7 @@ module ActionDispatch
|
|
1135
1251
|
get :new
|
1136
1252
|
end if parent_resource.actions.include?(:new)
|
1137
1253
|
|
1138
|
-
|
1139
|
-
get :edit if parent_resource.actions.include?(:edit)
|
1140
|
-
get :show if parent_resource.actions.include?(:show)
|
1141
|
-
put :update if parent_resource.actions.include?(:update)
|
1142
|
-
delete :destroy if parent_resource.actions.include?(:destroy)
|
1143
|
-
end
|
1254
|
+
set_member_mappings_for_resource
|
1144
1255
|
end
|
1145
1256
|
|
1146
1257
|
self
|
@@ -1246,6 +1357,9 @@ module ActionDispatch
|
|
1246
1357
|
parent_resource.instance_of?(Resource) && @scope[:shallow]
|
1247
1358
|
end
|
1248
1359
|
|
1360
|
+
# match 'path' => 'controller#action'
|
1361
|
+
# match 'path', to: 'controller#action'
|
1362
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1249
1363
|
def match(path, *rest)
|
1250
1364
|
if rest.empty? && Hash === path
|
1251
1365
|
options = path
|
@@ -1258,10 +1372,6 @@ module ActionDispatch
|
|
1258
1372
|
paths = [path] + rest
|
1259
1373
|
end
|
1260
1374
|
|
1261
|
-
if @scope[:controller] && @scope[:action]
|
1262
|
-
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1263
|
-
end
|
1264
|
-
|
1265
1375
|
path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
|
1266
1376
|
if using_match_shorthand?(path_without_format, options)
|
1267
1377
|
options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
|
@@ -1312,7 +1422,7 @@ module ActionDispatch
|
|
1312
1422
|
options[:as] = name_for_action(options[:as], action)
|
1313
1423
|
end
|
1314
1424
|
|
1315
|
-
mapping = Mapping.new(@set, @scope, path, options)
|
1425
|
+
mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
|
1316
1426
|
app, conditions, requirements, defaults, as, anchor = mapping.to_route
|
1317
1427
|
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
1318
1428
|
end
|
@@ -1418,18 +1528,18 @@ module ActionDispatch
|
|
1418
1528
|
def nested_options #:nodoc:
|
1419
1529
|
options = { :as => parent_resource.member_name }
|
1420
1530
|
options[:constraints] = {
|
1421
|
-
|
1422
|
-
} if
|
1531
|
+
parent_resource.nested_param => param_constraint
|
1532
|
+
} if param_constraint?
|
1423
1533
|
|
1424
1534
|
options
|
1425
1535
|
end
|
1426
1536
|
|
1427
|
-
def
|
1428
|
-
@scope[:constraints] && @scope[:constraints][
|
1537
|
+
def param_constraint? #:nodoc:
|
1538
|
+
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
|
1429
1539
|
end
|
1430
1540
|
|
1431
|
-
def
|
1432
|
-
@scope[:constraints][
|
1541
|
+
def param_constraint #:nodoc:
|
1542
|
+
@scope[:constraints][parent_resource.param]
|
1433
1543
|
end
|
1434
1544
|
|
1435
1545
|
def canonical_action?(action, flag) #:nodoc:
|
@@ -1442,9 +1552,9 @@ module ActionDispatch
|
|
1442
1552
|
|
1443
1553
|
def path_for_action(action, path) #:nodoc:
|
1444
1554
|
prefix = shallow_scoping? ?
|
1445
|
-
"#{@scope[:shallow_path]}/#{parent_resource.
|
1555
|
+
"#{@scope[:shallow_path]}/#{parent_resource.shallow_scope}" : @scope[:path]
|
1446
1556
|
|
1447
|
-
|
1557
|
+
if canonical_action?(action, path.blank?)
|
1448
1558
|
prefix.to_s
|
1449
1559
|
else
|
1450
1560
|
"#{prefix}/#{action_path(action, path)}"
|
@@ -1452,8 +1562,7 @@ module ActionDispatch
|
|
1452
1562
|
end
|
1453
1563
|
|
1454
1564
|
def action_path(name, path = nil) #:nodoc:
|
1455
|
-
|
1456
|
-
name = name.to_sym if name.is_a?(String) && !name.empty?
|
1565
|
+
name = name.to_sym if name.is_a?(String)
|
1457
1566
|
path || @scope[:path_names][name] || name.to_s
|
1458
1567
|
end
|
1459
1568
|
|
@@ -1503,17 +1612,136 @@ module ActionDispatch
|
|
1503
1612
|
end
|
1504
1613
|
end
|
1505
1614
|
end
|
1615
|
+
|
1616
|
+
def set_member_mappings_for_resource
|
1617
|
+
member do
|
1618
|
+
get :edit if parent_resource.actions.include?(:edit)
|
1619
|
+
get :show if parent_resource.actions.include?(:show)
|
1620
|
+
if parent_resource.actions.include?(:update)
|
1621
|
+
patch :update
|
1622
|
+
put :update
|
1623
|
+
end
|
1624
|
+
delete :destroy if parent_resource.actions.include?(:destroy)
|
1625
|
+
end
|
1626
|
+
end
|
1627
|
+
end
|
1628
|
+
|
1629
|
+
# Routing Concerns allow you to declare common routes that can be reused
|
1630
|
+
# inside others resources and routes.
|
1631
|
+
#
|
1632
|
+
# concern :commentable do
|
1633
|
+
# resources :comments
|
1634
|
+
# end
|
1635
|
+
#
|
1636
|
+
# concern :image_attachable do
|
1637
|
+
# resources :images, only: :index
|
1638
|
+
# end
|
1639
|
+
#
|
1640
|
+
# These concerns are used in Resources routing:
|
1641
|
+
#
|
1642
|
+
# resources :messages, concerns: [:commentable, :image_attachable]
|
1643
|
+
#
|
1644
|
+
# or in a scope or namespace:
|
1645
|
+
#
|
1646
|
+
# namespace :posts do
|
1647
|
+
# concerns :commentable
|
1648
|
+
# end
|
1649
|
+
module Concerns
|
1650
|
+
# Define a routing concern using a name.
|
1651
|
+
#
|
1652
|
+
# Concerns may be defined inline, using a block, or handled by
|
1653
|
+
# another object, by passing that object as the second parameter.
|
1654
|
+
#
|
1655
|
+
# The concern object, if supplied, should respond to <tt>call</tt>,
|
1656
|
+
# which will receive two parameters:
|
1657
|
+
#
|
1658
|
+
# * The current mapper
|
1659
|
+
# * A hash of options which the concern object may use
|
1660
|
+
#
|
1661
|
+
# Options may also be used by concerns defined in a block by accepting
|
1662
|
+
# a block parameter. So, using a block, you might do something as
|
1663
|
+
# simple as limit the actions available on certain resources, passing
|
1664
|
+
# standard resource options through the concern:
|
1665
|
+
#
|
1666
|
+
# concern :commentable do |options|
|
1667
|
+
# resources :comments, options
|
1668
|
+
# end
|
1669
|
+
#
|
1670
|
+
# resources :posts, concerns: :commentable
|
1671
|
+
# resources :archived_posts do
|
1672
|
+
# # Don't allow comments on archived posts
|
1673
|
+
# concerns :commentable, only: [:index, :show]
|
1674
|
+
# end
|
1675
|
+
#
|
1676
|
+
# Or, using a callable object, you might implement something more
|
1677
|
+
# specific to your application, which would be out of place in your
|
1678
|
+
# routes file.
|
1679
|
+
#
|
1680
|
+
# # purchasable.rb
|
1681
|
+
# class Purchasable
|
1682
|
+
# def initialize(defaults = {})
|
1683
|
+
# @defaults = defaults
|
1684
|
+
# end
|
1685
|
+
#
|
1686
|
+
# def call(mapper, options = {})
|
1687
|
+
# options = @defaults.merge(options)
|
1688
|
+
# mapper.resources :purchases
|
1689
|
+
# mapper.resources :receipts
|
1690
|
+
# mapper.resources :returns if options[:returnable]
|
1691
|
+
# end
|
1692
|
+
# end
|
1693
|
+
#
|
1694
|
+
# # routes.rb
|
1695
|
+
# concern :purchasable, Purchasable.new(returnable: true)
|
1696
|
+
#
|
1697
|
+
# resources :toys, concerns: :purchasable
|
1698
|
+
# resources :electronics, concerns: :purchasable
|
1699
|
+
# resources :pets do
|
1700
|
+
# concerns :purchasable, returnable: false
|
1701
|
+
# end
|
1702
|
+
#
|
1703
|
+
# Any routing helpers can be used inside a concern. If using a
|
1704
|
+
# callable, they're accessible from the Mapper that's passed to
|
1705
|
+
# <tt>call</tt>.
|
1706
|
+
def concern(name, callable = nil, &block)
|
1707
|
+
callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
|
1708
|
+
@concerns[name] = callable
|
1709
|
+
end
|
1710
|
+
|
1711
|
+
# Use the named concerns
|
1712
|
+
#
|
1713
|
+
# resources :posts do
|
1714
|
+
# concerns :commentable
|
1715
|
+
# end
|
1716
|
+
#
|
1717
|
+
# concerns also work in any routes helper that you want to use:
|
1718
|
+
#
|
1719
|
+
# namespace :posts do
|
1720
|
+
# concerns :commentable
|
1721
|
+
# end
|
1722
|
+
def concerns(*args)
|
1723
|
+
options = args.extract_options!
|
1724
|
+
args.flatten.each do |name|
|
1725
|
+
if concern = @concerns[name]
|
1726
|
+
concern.call(self, options)
|
1727
|
+
else
|
1728
|
+
raise ArgumentError, "No concern named #{name} was found!"
|
1729
|
+
end
|
1730
|
+
end
|
1731
|
+
end
|
1506
1732
|
end
|
1507
1733
|
|
1508
1734
|
def initialize(set) #:nodoc:
|
1509
1735
|
@set = set
|
1510
1736
|
@scope = { :path_names => @set.resources_path_names }
|
1737
|
+
@concerns = {}
|
1511
1738
|
end
|
1512
1739
|
|
1513
1740
|
include Base
|
1514
1741
|
include HttpHelpers
|
1515
1742
|
include Redirection
|
1516
1743
|
include Scoping
|
1744
|
+
include Concerns
|
1517
1745
|
include Resources
|
1518
1746
|
end
|
1519
1747
|
end
|