actionpack 4.0.0.beta1 → 4.0.0.rc1
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 +195 -11
- data/lib/abstract_controller/base.rb +1 -1
- data/lib/abstract_controller/helpers.rb +2 -2
- data/lib/abstract_controller/layouts.rb +10 -5
- data/lib/abstract_controller/rendering.rb +11 -3
- data/lib/abstract_controller/translation.rb +1 -1
- data/lib/action_controller/log_subscriber.rb +5 -0
- data/lib/action_controller/metal.rb +2 -3
- data/lib/action_controller/metal/force_ssl.rb +52 -17
- data/lib/action_controller/metal/helpers.rb +0 -1
- data/lib/action_controller/metal/hide_actions.rb +1 -1
- data/lib/action_controller/metal/http_authentication.rb +3 -2
- data/lib/action_controller/metal/live.rb +34 -0
- data/lib/action_controller/metal/rendering.rb +1 -1
- data/lib/action_controller/metal/strong_parameters.rb +7 -3
- data/lib/action_controller/test_case.rb +45 -11
- data/lib/action_dispatch.rb +4 -6
- data/lib/action_dispatch/http/cache.rb +2 -2
- data/lib/action_dispatch/http/headers.rb +39 -15
- data/lib/action_dispatch/http/mime_negotiation.rb +1 -1
- data/lib/action_dispatch/http/mime_type.rb +11 -3
- data/lib/action_dispatch/http/parameters.rb +17 -24
- data/lib/action_dispatch/http/request.rb +17 -2
- data/lib/action_dispatch/http/response.rb +2 -1
- data/lib/action_dispatch/http/upload.rb +5 -5
- data/lib/action_dispatch/http/url.rb +53 -12
- data/lib/action_dispatch/journey/formatter.rb +1 -1
- data/lib/action_dispatch/journey/path/pattern.rb +1 -1
- data/lib/action_dispatch/journey/route.rb +8 -0
- data/lib/action_dispatch/journey/router.rb +3 -1
- data/lib/action_dispatch/journey/visitors.rb +8 -0
- data/lib/action_dispatch/middleware/cookies.rb +169 -135
- data/lib/action_dispatch/middleware/exception_wrapper.rb +1 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +2 -2
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/session/cookie_store.rb +38 -58
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +4 -6
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +1 -1
- data/lib/action_dispatch/routing.rb +28 -64
- data/lib/action_dispatch/routing/mapper.rb +61 -48
- data/lib/action_dispatch/routing/route_set.rb +17 -14
- data/lib/action_dispatch/testing/assertions/routing.rb +2 -2
- data/lib/action_dispatch/testing/assertions/selector.rb +2 -2
- data/lib/action_dispatch/testing/integration.rb +36 -35
- data/lib/action_dispatch/testing/test_process.rb +1 -1
- data/lib/action_pack/version.rb +7 -6
- data/lib/action_view/buffers.rb +6 -0
- data/lib/action_view/dependency_tracker.rb +3 -1
- data/lib/action_view/helpers/asset_tag_helper.rb +13 -8
- data/lib/action_view/helpers/capture_helper.rb +2 -2
- data/lib/action_view/helpers/date_helper.rb +1 -1
- data/lib/action_view/helpers/form_helper.rb +56 -19
- data/lib/action_view/helpers/form_options_helper.rb +3 -3
- data/lib/action_view/helpers/form_tag_helper.rb +1 -1
- data/lib/action_view/helpers/javascript_helper.rb +2 -2
- data/lib/action_view/helpers/number_helper.rb +25 -0
- data/lib/action_view/helpers/tags/base.rb +9 -10
- data/lib/action_view/helpers/tags/check_box.rb +1 -1
- data/lib/action_view/helpers/tags/checkable.rb +2 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +3 -3
- data/lib/action_view/helpers/tags/collection_helpers.rb +3 -3
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -3
- data/lib/action_view/helpers/tags/collection_select.rb +1 -1
- data/lib/action_view/helpers/tags/color_field.rb +2 -2
- data/lib/action_view/helpers/tags/date_field.rb +2 -2
- data/lib/action_view/helpers/tags/date_select.rb +2 -2
- data/lib/action_view/helpers/tags/datetime_field.rb +2 -2
- data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -2
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -2
- data/lib/action_view/helpers/tags/email_field.rb +2 -2
- data/lib/action_view/helpers/tags/file_field.rb +2 -2
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -2
- data/lib/action_view/helpers/tags/label.rb +2 -2
- data/lib/action_view/helpers/tags/month_field.rb +2 -2
- data/lib/action_view/helpers/tags/number_field.rb +2 -2
- data/lib/action_view/helpers/tags/password_field.rb +2 -2
- data/lib/action_view/helpers/tags/radio_button.rb +2 -2
- data/lib/action_view/helpers/tags/range_field.rb +2 -2
- data/lib/action_view/helpers/tags/search_field.rb +2 -2
- data/lib/action_view/helpers/tags/select.rb +2 -3
- data/lib/action_view/helpers/tags/tel_field.rb +2 -2
- data/lib/action_view/helpers/tags/text_area.rb +2 -2
- data/lib/action_view/helpers/tags/text_field.rb +2 -2
- data/lib/action_view/helpers/tags/time_field.rb +2 -2
- data/lib/action_view/helpers/tags/time_select.rb +2 -2
- data/lib/action_view/helpers/tags/time_zone_select.rb +2 -2
- data/lib/action_view/helpers/tags/url_field.rb +2 -2
- data/lib/action_view/helpers/tags/week_field.rb +2 -2
- data/lib/action_view/helpers/text_helper.rb +8 -5
- data/lib/action_view/helpers/url_helper.rb +18 -6
- data/lib/action_view/lookup_context.rb +7 -1
- data/lib/action_view/path_set.rb +6 -0
- data/lib/action_view/renderer/abstract_renderer.rb +15 -0
- data/lib/action_view/renderer/partial_renderer.rb +14 -0
- data/lib/action_view/renderer/renderer.rb +6 -0
- data/lib/action_view/template.rb +3 -2
- data/lib/action_view/template/handlers/erb.rb +29 -3
- data/lib/action_view/template/resolver.rb +3 -3
- data/lib/action_view/test_case.rb +1 -0
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +5 -5
- data/lib/action_view/vendor/html-scanner/html/selector.rb +8 -8
- metadata +8 -8
@@ -9,6 +9,7 @@ module ActionDispatch
|
|
9
9
|
'ActionController::RoutingError' => :not_found,
|
10
10
|
'AbstractController::ActionNotFound' => :not_found,
|
11
11
|
'ActionController::MethodNotAllowed' => :method_not_allowed,
|
12
|
+
'ActionController::UnknownHttpMethod' => :method_not_allowed,
|
12
13
|
'ActionController::NotImplemented' => :not_implemented,
|
13
14
|
'ActionController::UnknownFormat' => :not_acceptable,
|
14
15
|
'ActionController::InvalidAuthenticityToken' => :unprocessable_entity,
|
@@ -9,7 +9,7 @@ module ActionDispatch
|
|
9
9
|
# at GetIp#calculate_ip.
|
10
10
|
#
|
11
11
|
# Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
|
12
|
-
# requires. Some Rack servers simply drop
|
12
|
+
# requires. Some Rack servers simply drop preceding headers, and only report
|
13
13
|
# the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
|
14
14
|
# If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn)
|
15
15
|
# then you should test your Rack server to make sure your data is good.
|
@@ -101,7 +101,7 @@ module ActionDispatch
|
|
101
101
|
(([0-9A-Fa-f]{1,4}:){0,4}:([0-9A-Fa-f]{1,4}:){1}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
102
102
|
(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d) |(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)) | # ip v6 with compatible to v4
|
103
103
|
([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4}) | # ip v6 with compatible to v4
|
104
|
-
(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the
|
104
|
+
(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4}) | # ip v6 with double colon at the beginning
|
105
105
|
(([0-9A-Fa-f]{1,4}:){1,7}:) # ip v6 without ending
|
106
106
|
)$)
|
107
107
|
}x
|
@@ -18,7 +18,7 @@ module ActionDispatch
|
|
18
18
|
|
19
19
|
def call(env)
|
20
20
|
env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
|
21
|
-
@app.call(env).tap { |
|
21
|
+
@app.call(env).tap { |_status, headers, _body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
@@ -4,36 +4,51 @@ require 'rack/session/cookie'
|
|
4
4
|
|
5
5
|
module ActionDispatch
|
6
6
|
module Session
|
7
|
-
# This cookie-based session store is the Rails default.
|
8
|
-
#
|
9
|
-
# size limit. Cookie-based sessions are dramatically faster than the
|
10
|
-
# alternatives.
|
7
|
+
# This cookie-based session store is the Rails default. It is
|
8
|
+
# dramatically faster than the alternatives.
|
11
9
|
#
|
12
|
-
#
|
13
|
-
#
|
10
|
+
# Sessions typically contain at most a user_id and flash message; both fit
|
11
|
+
# within the 4K cookie size limit. A CookieOverflow exception is raised if
|
12
|
+
# you attempt to store more than 4K of data.
|
14
13
|
#
|
15
|
-
#
|
14
|
+
# The cookie jar used for storage is automatically configured to be the
|
15
|
+
# best possible option given your application's configuration.
|
16
16
|
#
|
17
|
-
#
|
18
|
-
# a user cannot alter his +user_id+ without
|
19
|
-
#
|
20
|
-
#
|
17
|
+
# If you only have secret_token set, your cookies will be signed, but
|
18
|
+
# not encrypted. This means a user cannot alter his +user_id+ without
|
19
|
+
# knowing your app's secret key, but can easily read his +user_id+. This
|
20
|
+
# was the default for Rails 3 apps.
|
21
21
|
#
|
22
|
-
#
|
22
|
+
# If you have secret_key_base set, your cookies will be encrypted. This
|
23
|
+
# goes a step further than signed cookies in that encrypted cookies cannot
|
24
|
+
# be altered or read by users. This is the default starting in Rails 4.
|
23
25
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# more than 30 characters.
|
26
|
+
# If you have both secret_token and secret_key base set, your cookies will
|
27
|
+
# be encrypted, and signed cookies generated by Rails 3 will be
|
28
|
+
# transparently read and encrypted to provide a smooth upgrade path.
|
28
29
|
#
|
29
|
-
#
|
30
|
+
# Configure your session store in config/initializers/session_store.rb:
|
30
31
|
#
|
31
|
-
#
|
32
|
-
# integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
|
33
|
-
# such as 'MD5', 'RIPEMD160', 'SHA256', etc.
|
32
|
+
# Myapp::Application.config.session_store :cookie_store, key: '_your_app_session'
|
34
33
|
#
|
35
|
-
#
|
36
|
-
#
|
34
|
+
# Configure your secret key in config/initializers/secret_token.rb:
|
35
|
+
#
|
36
|
+
# Myapp::Application.config.secret_key_base 'secret key'
|
37
|
+
#
|
38
|
+
# To generate a secret key for an existing application, run `rake secret`.
|
39
|
+
#
|
40
|
+
# If you are upgrading an existing Rails 3 app, you should leave your
|
41
|
+
# existing secret_token in place and simply add the new secret_key_base.
|
42
|
+
# Note that you should wait to set secret_key_base until you have 100% of
|
43
|
+
# your userbase on Rails 4 and are reasonably sure you will not need to
|
44
|
+
# rollback to Rails 3. This is because cookies signed based on the new
|
45
|
+
# secret_key_base in Rails 4 are not backwards compatible with Rails 3.
|
46
|
+
# You are free to leave your existing secret_token in place, not set the
|
47
|
+
# new secret_key_base, and ignore the deprecation warnings until you are
|
48
|
+
# reasonably sure that your upgrade is otherwise complete. Additionally,
|
49
|
+
# you should take care to make sure you are not relying on the ability to
|
50
|
+
# decode signed cookies generated by your app in external applications or
|
51
|
+
# Javascript before upgrading.
|
37
52
|
#
|
38
53
|
# Note that changing digest or secret invalidates all existing sessions!
|
39
54
|
class CookieStore < Rack::Session::Abstract::ID
|
@@ -100,42 +115,7 @@ module ActionDispatch
|
|
100
115
|
|
101
116
|
def cookie_jar(env)
|
102
117
|
request = ActionDispatch::Request.new(env)
|
103
|
-
request.cookie_jar.
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
class EncryptedCookieStore < CookieStore
|
108
|
-
|
109
|
-
private
|
110
|
-
|
111
|
-
def cookie_jar(env)
|
112
|
-
request = ActionDispatch::Request.new(env)
|
113
|
-
request.cookie_jar.encrypted
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
# This cookie store helps you upgrading apps that use +CookieStore+ to the new default +EncryptedCookieStore+
|
118
|
-
# To use this CookieStore set
|
119
|
-
#
|
120
|
-
# Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'
|
121
|
-
#
|
122
|
-
# in your config/initializers/session_store.rb
|
123
|
-
#
|
124
|
-
# You will also need to add
|
125
|
-
#
|
126
|
-
# Myapp::Application.config.secret_key_base = 'some secret'
|
127
|
-
#
|
128
|
-
# in your config/initializers/secret_token.rb, but do not remove +Myapp::Application.config.secret_token = 'some secret'+
|
129
|
-
class UpgradeSignatureToEncryptionCookieStore < EncryptedCookieStore
|
130
|
-
private
|
131
|
-
|
132
|
-
def get_cookie(env)
|
133
|
-
signed_using_old_secret_cookie_jar(env)[@key] || cookie_jar(env)[@key]
|
134
|
-
end
|
135
|
-
|
136
|
-
def signed_using_old_secret_cookie_jar(env)
|
137
|
-
request = ActionDispatch::Request.new(env)
|
138
|
-
request.cookie_jar.signed_using_old_secret
|
118
|
+
request.cookie_jar.signed_or_encrypted
|
139
119
|
end
|
140
120
|
end
|
141
121
|
end
|
@@ -13,7 +13,7 @@
|
|
13
13
|
request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n")
|
14
14
|
|
15
15
|
def debug_hash(object)
|
16
|
-
object.to_hash.sort_by { |k,
|
16
|
+
object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
|
17
17
|
end unless self.class.method_defined?(:debug_hash)
|
18
18
|
%>
|
19
19
|
|
@@ -1,10 +1,8 @@
|
|
1
1
|
<%
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
]
|
7
|
-
names = traces.collect {|name, trace| name}
|
2
|
+
traces = { "Application Trace" => @application_trace,
|
3
|
+
"Framework Trace" => @framework_trace,
|
4
|
+
"Full Trace" => @full_trace }
|
5
|
+
names = traces.keys
|
8
6
|
%>
|
9
7
|
|
10
8
|
<p><code>Rails.root: <%= defined?(Rails) && Rails.respond_to?(:root) ? Rails.root : "unset" %></code></p>
|
@@ -8,7 +8,7 @@
|
|
8
8
|
<h2>Failure reasons:</h2>
|
9
9
|
<ol>
|
10
10
|
<% @exception.failures.each do |route, reason| %>
|
11
|
-
<li><code><%= route.inspect.
|
11
|
+
<li><code><%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %></li>
|
12
12
|
<% end %>
|
13
13
|
</ol>
|
14
14
|
</p>
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<header>
|
3
3
|
<h1>
|
4
4
|
<%= @exception.original_exception.class.to_s %> in
|
5
|
-
<%= @request.parameters["controller"].
|
5
|
+
<%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
|
6
6
|
</h1>
|
7
7
|
</header>
|
8
8
|
|
@@ -69,6 +69,22 @@ module ActionDispatch
|
|
69
69
|
# <tt>Routing::Mapper::Scoping#namespace</tt>, and
|
70
70
|
# <tt>Routing::Mapper::Scoping#scope</tt>.
|
71
71
|
#
|
72
|
+
# == Non-resourceful routes
|
73
|
+
#
|
74
|
+
# For routes that don't fit the <tt>resources</tt> mold, you can use the HTTP helper
|
75
|
+
# methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
|
76
|
+
#
|
77
|
+
# get 'post/:id' => 'posts#show'
|
78
|
+
# post 'post/:id' => 'posts#create_comment'
|
79
|
+
#
|
80
|
+
# If your route needs to respond to more than one HTTP method (or all methods) then using the
|
81
|
+
# <tt>:via</tt> option on <tt>match</tt> is preferable.
|
82
|
+
#
|
83
|
+
# match 'post/:id' => 'posts#show', via: [:get, :post]
|
84
|
+
#
|
85
|
+
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
|
86
|
+
# URL will route to the <tt>show</tt> action.
|
87
|
+
#
|
72
88
|
# == Named routes
|
73
89
|
#
|
74
90
|
# Routes can be named by passing an <tt>:as</tt> option,
|
@@ -78,7 +94,7 @@ module ActionDispatch
|
|
78
94
|
# Example:
|
79
95
|
#
|
80
96
|
# # In routes.rb
|
81
|
-
#
|
97
|
+
# get '/login' => 'accounts#login', as: 'login'
|
82
98
|
#
|
83
99
|
# # With render, redirect_to, tests, etc.
|
84
100
|
# redirect_to login_url
|
@@ -104,9 +120,9 @@ module ActionDispatch
|
|
104
120
|
#
|
105
121
|
# # In routes.rb
|
106
122
|
# controller :blog do
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
123
|
+
# get 'blog/show' => :list
|
124
|
+
# get 'blog/delete' => :delete
|
125
|
+
# get 'blog/edit/:id' => :edit
|
110
126
|
# end
|
111
127
|
#
|
112
128
|
# # provides named routes for show, delete, and edit
|
@@ -116,7 +132,7 @@ module ActionDispatch
|
|
116
132
|
#
|
117
133
|
# Routes can generate pretty URLs. For example:
|
118
134
|
#
|
119
|
-
#
|
135
|
+
# get '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
|
120
136
|
# year: /\d{4}/,
|
121
137
|
# month: /\d{1,2}/,
|
122
138
|
# day: /\d{1,2}/
|
@@ -131,7 +147,7 @@ module ActionDispatch
|
|
131
147
|
# You can specify a regular expression to define a format for a parameter.
|
132
148
|
#
|
133
149
|
# controller 'geocode' do
|
134
|
-
#
|
150
|
+
# get 'geocode/:postalcode' => :show, constraints: {
|
135
151
|
# postalcode: /\d{5}(-\d{4})?/
|
136
152
|
# }
|
137
153
|
#
|
@@ -139,13 +155,13 @@ module ActionDispatch
|
|
139
155
|
# expression modifiers:
|
140
156
|
#
|
141
157
|
# controller 'geocode' do
|
142
|
-
#
|
158
|
+
# get 'geocode/:postalcode' => :show, constraints: {
|
143
159
|
# postalcode: /hx\d\d\s\d[a-z]{2}/i
|
144
160
|
# }
|
145
161
|
# end
|
146
162
|
#
|
147
163
|
# controller 'geocode' do
|
148
|
-
#
|
164
|
+
# get 'geocode/:postalcode' => :show, constraints: {
|
149
165
|
# postalcode: /# Postcode format
|
150
166
|
# \d{5} #Prefix
|
151
167
|
# (-\d{4})? #Suffix
|
@@ -153,73 +169,21 @@ module ActionDispatch
|
|
153
169
|
# }
|
154
170
|
# end
|
155
171
|
#
|
156
|
-
# Using the multiline
|
172
|
+
# Using the multiline modifier will raise an +ArgumentError+.
|
157
173
|
# Encoding regular expression modifiers are silently ignored. The
|
158
174
|
# match will always use the default encoding or ASCII.
|
159
175
|
#
|
160
|
-
# == Default route
|
161
|
-
#
|
162
|
-
# Consider the following route, which you will find commented out at the
|
163
|
-
# bottom of your generated <tt>config/routes.rb</tt>:
|
164
|
-
#
|
165
|
-
# match ':controller(/:action(/:id))(.:format)'
|
166
|
-
#
|
167
|
-
# This route states that it expects requests to consist of a
|
168
|
-
# <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
|
169
|
-
# turn is followed optionally by an <tt>:id</tt>, which in turn is followed
|
170
|
-
# optionally by a <tt>:format</tt>.
|
171
|
-
#
|
172
|
-
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
|
173
|
-
# up with:
|
174
|
-
#
|
175
|
-
# params = { controller: 'blog',
|
176
|
-
# action: 'edit',
|
177
|
-
# id: '22'
|
178
|
-
# }
|
179
|
-
#
|
180
|
-
# By not relying on default routes, you improve the security of your
|
181
|
-
# application since not all controller actions, which includes actions you
|
182
|
-
# might add at a later time, are exposed by default.
|
183
|
-
#
|
184
|
-
# == HTTP Methods
|
185
|
-
#
|
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.
|
193
|
-
#
|
194
|
-
# match 'post/:id' => 'posts#show', via: :get
|
195
|
-
# match 'post/:id' => 'posts#create_comment', via: :post
|
196
|
-
#
|
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
|
198
|
-
# URL will route to the <tt>show</tt> action.
|
199
|
-
#
|
200
|
-
# === HTTP helper methods
|
201
|
-
#
|
202
|
-
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
|
203
|
-
# methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>.
|
204
|
-
#
|
205
|
-
# get 'post/:id' => 'posts#show'
|
206
|
-
# post 'post/:id' => 'posts#create_comment'
|
207
|
-
#
|
208
|
-
# This syntax is less verbose and the intention is more apparent to someone else reading your code,
|
209
|
-
# however if your route needs to respond to more than one HTTP method (or all methods) then using the
|
210
|
-
# <tt>:via</tt> option on <tt>match</tt> is preferable.
|
211
|
-
#
|
212
176
|
# == External redirects
|
213
177
|
#
|
214
178
|
# You can redirect any path to another path using the redirect helper in your router:
|
215
179
|
#
|
216
|
-
#
|
180
|
+
# get "/stories" => redirect("/posts")
|
217
181
|
#
|
218
182
|
# == Unicode character routes
|
219
183
|
#
|
220
184
|
# You can specify unicode character routes in your router:
|
221
185
|
#
|
222
|
-
#
|
186
|
+
# get "こんにちは" => "welcome#index"
|
223
187
|
#
|
224
188
|
# == Routing to Rack Applications
|
225
189
|
#
|
@@ -227,7 +191,7 @@ module ActionDispatch
|
|
227
191
|
# index action in the PostsController, you can specify any Rack application
|
228
192
|
# as the endpoint for a matcher:
|
229
193
|
#
|
230
|
-
#
|
194
|
+
# get "/application.js" => Sprockets
|
231
195
|
#
|
232
196
|
# == Reloading routes
|
233
197
|
#
|
@@ -10,6 +10,9 @@ module ActionDispatch
|
|
10
10
|
module Routing
|
11
11
|
class Mapper
|
12
12
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
13
|
+
SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
14
|
+
:controller, :path_names, :constraints, :defaults,
|
15
|
+
:shallow, :blocks, :options]
|
13
16
|
|
14
17
|
class Constraints #:nodoc:
|
15
18
|
def self.new(app, constraints, request = Rack::Request)
|
@@ -58,8 +61,8 @@ module ActionDispatch
|
|
58
61
|
@set, @scope, @path, @options = set, scope, path, options
|
59
62
|
@requirements, @conditions, @defaults = {}, {}, {}
|
60
63
|
|
61
|
-
normalize_path!
|
62
64
|
normalize_options!
|
65
|
+
normalize_path!
|
63
66
|
normalize_requirements!
|
64
67
|
normalize_conditions!
|
65
68
|
normalize_defaults!
|
@@ -113,31 +116,15 @@ module ActionDispatch
|
|
113
116
|
@options.merge!(default_controller_and_action)
|
114
117
|
end
|
115
118
|
|
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
119
|
def normalize_requirements!
|
125
120
|
constraints.each do |key, requirement|
|
126
121
|
next unless segment_keys.include?(key) || key == :controller
|
127
|
-
|
128
|
-
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
129
|
-
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
130
|
-
end
|
131
|
-
|
132
|
-
if requirement.multiline?
|
133
|
-
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
134
|
-
end
|
135
|
-
|
122
|
+
verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
|
136
123
|
@requirements[key] = requirement
|
137
124
|
end
|
138
125
|
|
139
126
|
if options[:format] == true
|
140
|
-
@requirements[:format]
|
127
|
+
@requirements[:format] ||= /.+/
|
141
128
|
elsif Regexp === options[:format]
|
142
129
|
@requirements[:format] = options[:format]
|
143
130
|
elsif String === options[:format]
|
@@ -145,6 +132,16 @@ module ActionDispatch
|
|
145
132
|
end
|
146
133
|
end
|
147
134
|
|
135
|
+
def verify_regexp_requirement(requirement)
|
136
|
+
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
137
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
138
|
+
end
|
139
|
+
|
140
|
+
if requirement.multiline?
|
141
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
148
145
|
def normalize_defaults!
|
149
146
|
@defaults.merge!(scope[:defaults]) if scope[:defaults]
|
150
147
|
@defaults.merge!(options[:defaults]) if options[:defaults]
|
@@ -187,7 +184,8 @@ module ActionDispatch
|
|
187
184
|
|
188
185
|
if !via_all && options[:via].blank?
|
189
186
|
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,
|
187
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
188
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
191
189
|
" Instead of: match \"controller#action\"\n" \
|
192
190
|
" Do: get \"controller#action\""
|
193
191
|
raise msg
|
@@ -329,7 +327,6 @@ module ActionDispatch
|
|
329
327
|
# because this means it will be matched first. As this is the most popular route
|
330
328
|
# of most Rails applications, this is beneficial.
|
331
329
|
def root(options = {})
|
332
|
-
options = { :to => options } if options.is_a?(String)
|
333
330
|
match '/', { :as => :root, :via => :get }.merge!(options)
|
334
331
|
end
|
335
332
|
|
@@ -426,11 +423,15 @@ module ActionDispatch
|
|
426
423
|
# end
|
427
424
|
#
|
428
425
|
# [:constraints]
|
429
|
-
# Constrains parameters with a hash of regular expressions
|
430
|
-
# object that responds to <tt>matches?</tt
|
426
|
+
# Constrains parameters with a hash of regular expressions
|
427
|
+
# or an object that responds to <tt>matches?</tt>. In addition, constraints
|
428
|
+
# other than path can also be specified with any object
|
429
|
+
# that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
|
431
430
|
#
|
432
431
|
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
|
433
432
|
#
|
433
|
+
# match 'json_only', constraints: { format: 'json' }
|
434
|
+
#
|
434
435
|
# class Blacklist
|
435
436
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
436
437
|
# end
|
@@ -488,7 +489,7 @@ module ActionDispatch
|
|
488
489
|
end
|
489
490
|
|
490
491
|
options = app
|
491
|
-
app, path = options.find { |k,
|
492
|
+
app, path = options.find { |k, _| k.respond_to?(:call) }
|
492
493
|
options.delete(app) if app
|
493
494
|
end
|
494
495
|
|
@@ -591,8 +592,7 @@ module ActionDispatch
|
|
591
592
|
private
|
592
593
|
def map_method(method, args, &block)
|
593
594
|
options = args.extract_options!
|
594
|
-
options[:via]
|
595
|
-
options[:path] ||= args.first if args.first.is_a?(String)
|
595
|
+
options[:via] = method
|
596
596
|
match(*args, options, &block)
|
597
597
|
self
|
598
598
|
end
|
@@ -700,19 +700,21 @@ module ActionDispatch
|
|
700
700
|
block, options[:constraints] = options[:constraints], {}
|
701
701
|
end
|
702
702
|
|
703
|
-
|
704
|
-
if
|
703
|
+
SCOPE_OPTIONS.each do |option|
|
704
|
+
if option == :blocks
|
705
|
+
value = block
|
706
|
+
elsif option == :options
|
707
|
+
value = options
|
708
|
+
else
|
709
|
+
value = options.delete(option)
|
710
|
+
end
|
711
|
+
|
712
|
+
if value
|
705
713
|
recover[option] = @scope[option]
|
706
714
|
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
707
715
|
end
|
708
716
|
end
|
709
717
|
|
710
|
-
recover[:blocks] = @scope[:blocks]
|
711
|
-
@scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
|
712
|
-
|
713
|
-
recover[:options] = @scope[:options]
|
714
|
-
@scope[:options] = merge_options_scope(@scope[:options], options)
|
715
|
-
|
716
718
|
yield
|
717
719
|
self
|
718
720
|
ensure
|
@@ -843,10 +845,6 @@ module ActionDispatch
|
|
843
845
|
end
|
844
846
|
|
845
847
|
private
|
846
|
-
def scope_options #:nodoc:
|
847
|
-
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
|
848
|
-
end
|
849
|
-
|
850
848
|
def merge_path_scope(parent, child) #:nodoc:
|
851
849
|
Mapper.normalize_path("#{parent}/#{child}")
|
852
850
|
end
|
@@ -947,6 +945,8 @@ module ActionDispatch
|
|
947
945
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
948
946
|
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
949
947
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
948
|
+
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
949
|
+
RESOURCE_SCOPES = [:resource, :resources]
|
950
950
|
|
951
951
|
class Resource #:nodoc:
|
952
952
|
attr_reader :controller, :path, :options, :param
|
@@ -1363,7 +1363,7 @@ module ActionDispatch
|
|
1363
1363
|
def match(path, *rest)
|
1364
1364
|
if rest.empty? && Hash === path
|
1365
1365
|
options = path
|
1366
|
-
path, to = options.find { |name,
|
1366
|
+
path, to = options.find { |name, _value| name.is_a?(String) }
|
1367
1367
|
options[:to] = to
|
1368
1368
|
options.delete(path)
|
1369
1369
|
paths = [path]
|
@@ -1372,18 +1372,23 @@ module ActionDispatch
|
|
1372
1372
|
paths = [path] + rest
|
1373
1373
|
end
|
1374
1374
|
|
1375
|
-
path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
|
1376
|
-
if using_match_shorthand?(path_without_format, options)
|
1377
|
-
options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
|
1378
|
-
end
|
1379
|
-
|
1380
1375
|
options[:anchor] = true unless options.key?(:anchor)
|
1381
1376
|
|
1382
1377
|
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1383
1378
|
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1384
1379
|
end
|
1385
1380
|
|
1386
|
-
paths.each
|
1381
|
+
paths.each do |_path|
|
1382
|
+
route_options = options.dup
|
1383
|
+
route_options[:path] ||= _path if _path.is_a?(String)
|
1384
|
+
|
1385
|
+
path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
|
1386
|
+
if using_match_shorthand?(path_without_format, route_options)
|
1387
|
+
route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
|
1388
|
+
end
|
1389
|
+
|
1390
|
+
decomposed_match(_path, route_options)
|
1391
|
+
end
|
1387
1392
|
self
|
1388
1393
|
end
|
1389
1394
|
|
@@ -1427,7 +1432,15 @@ module ActionDispatch
|
|
1427
1432
|
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
1428
1433
|
end
|
1429
1434
|
|
1430
|
-
def root(options={})
|
1435
|
+
def root(path, options={})
|
1436
|
+
if path.is_a?(String)
|
1437
|
+
options[:to] = path
|
1438
|
+
elsif path.is_a?(Hash) and options.empty?
|
1439
|
+
options = path
|
1440
|
+
else
|
1441
|
+
raise ArgumentError, "must be called with a path and/or options"
|
1442
|
+
end
|
1443
|
+
|
1431
1444
|
if @scope[:scope_level] == :resources
|
1432
1445
|
with_scope_level(:root) do
|
1433
1446
|
scope(parent_resource.path) do
|
@@ -1488,11 +1501,11 @@ module ActionDispatch
|
|
1488
1501
|
end
|
1489
1502
|
|
1490
1503
|
def resource_scope? #:nodoc:
|
1491
|
-
|
1504
|
+
RESOURCE_SCOPES.include? @scope[:scope_level]
|
1492
1505
|
end
|
1493
1506
|
|
1494
1507
|
def resource_method_scope? #:nodoc:
|
1495
|
-
|
1508
|
+
RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
|
1496
1509
|
end
|
1497
1510
|
|
1498
1511
|
def with_exclusive_scope
|