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.

Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +195 -11
  3. data/lib/abstract_controller/base.rb +1 -1
  4. data/lib/abstract_controller/helpers.rb +2 -2
  5. data/lib/abstract_controller/layouts.rb +10 -5
  6. data/lib/abstract_controller/rendering.rb +11 -3
  7. data/lib/abstract_controller/translation.rb +1 -1
  8. data/lib/action_controller/log_subscriber.rb +5 -0
  9. data/lib/action_controller/metal.rb +2 -3
  10. data/lib/action_controller/metal/force_ssl.rb +52 -17
  11. data/lib/action_controller/metal/helpers.rb +0 -1
  12. data/lib/action_controller/metal/hide_actions.rb +1 -1
  13. data/lib/action_controller/metal/http_authentication.rb +3 -2
  14. data/lib/action_controller/metal/live.rb +34 -0
  15. data/lib/action_controller/metal/rendering.rb +1 -1
  16. data/lib/action_controller/metal/strong_parameters.rb +7 -3
  17. data/lib/action_controller/test_case.rb +45 -11
  18. data/lib/action_dispatch.rb +4 -6
  19. data/lib/action_dispatch/http/cache.rb +2 -2
  20. data/lib/action_dispatch/http/headers.rb +39 -15
  21. data/lib/action_dispatch/http/mime_negotiation.rb +1 -1
  22. data/lib/action_dispatch/http/mime_type.rb +11 -3
  23. data/lib/action_dispatch/http/parameters.rb +17 -24
  24. data/lib/action_dispatch/http/request.rb +17 -2
  25. data/lib/action_dispatch/http/response.rb +2 -1
  26. data/lib/action_dispatch/http/upload.rb +5 -5
  27. data/lib/action_dispatch/http/url.rb +53 -12
  28. data/lib/action_dispatch/journey/formatter.rb +1 -1
  29. data/lib/action_dispatch/journey/path/pattern.rb +1 -1
  30. data/lib/action_dispatch/journey/route.rb +8 -0
  31. data/lib/action_dispatch/journey/router.rb +3 -1
  32. data/lib/action_dispatch/journey/visitors.rb +8 -0
  33. data/lib/action_dispatch/middleware/cookies.rb +169 -135
  34. data/lib/action_dispatch/middleware/exception_wrapper.rb +1 -0
  35. data/lib/action_dispatch/middleware/remote_ip.rb +2 -2
  36. data/lib/action_dispatch/middleware/request_id.rb +1 -1
  37. data/lib/action_dispatch/middleware/session/cookie_store.rb +38 -58
  38. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +1 -1
  39. data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +4 -6
  40. data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +1 -1
  41. data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +1 -1
  42. data/lib/action_dispatch/routing.rb +28 -64
  43. data/lib/action_dispatch/routing/mapper.rb +61 -48
  44. data/lib/action_dispatch/routing/route_set.rb +17 -14
  45. data/lib/action_dispatch/testing/assertions/routing.rb +2 -2
  46. data/lib/action_dispatch/testing/assertions/selector.rb +2 -2
  47. data/lib/action_dispatch/testing/integration.rb +36 -35
  48. data/lib/action_dispatch/testing/test_process.rb +1 -1
  49. data/lib/action_pack/version.rb +7 -6
  50. data/lib/action_view/buffers.rb +6 -0
  51. data/lib/action_view/dependency_tracker.rb +3 -1
  52. data/lib/action_view/helpers/asset_tag_helper.rb +13 -8
  53. data/lib/action_view/helpers/capture_helper.rb +2 -2
  54. data/lib/action_view/helpers/date_helper.rb +1 -1
  55. data/lib/action_view/helpers/form_helper.rb +56 -19
  56. data/lib/action_view/helpers/form_options_helper.rb +3 -3
  57. data/lib/action_view/helpers/form_tag_helper.rb +1 -1
  58. data/lib/action_view/helpers/javascript_helper.rb +2 -2
  59. data/lib/action_view/helpers/number_helper.rb +25 -0
  60. data/lib/action_view/helpers/tags/base.rb +9 -10
  61. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  62. data/lib/action_view/helpers/tags/checkable.rb +2 -2
  63. data/lib/action_view/helpers/tags/collection_check_boxes.rb +3 -3
  64. data/lib/action_view/helpers/tags/collection_helpers.rb +3 -3
  65. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -3
  66. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  67. data/lib/action_view/helpers/tags/color_field.rb +2 -2
  68. data/lib/action_view/helpers/tags/date_field.rb +2 -2
  69. data/lib/action_view/helpers/tags/date_select.rb +2 -2
  70. data/lib/action_view/helpers/tags/datetime_field.rb +2 -2
  71. data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -2
  72. data/lib/action_view/helpers/tags/datetime_select.rb +2 -2
  73. data/lib/action_view/helpers/tags/email_field.rb +2 -2
  74. data/lib/action_view/helpers/tags/file_field.rb +2 -2
  75. data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
  76. data/lib/action_view/helpers/tags/hidden_field.rb +2 -2
  77. data/lib/action_view/helpers/tags/label.rb +2 -2
  78. data/lib/action_view/helpers/tags/month_field.rb +2 -2
  79. data/lib/action_view/helpers/tags/number_field.rb +2 -2
  80. data/lib/action_view/helpers/tags/password_field.rb +2 -2
  81. data/lib/action_view/helpers/tags/radio_button.rb +2 -2
  82. data/lib/action_view/helpers/tags/range_field.rb +2 -2
  83. data/lib/action_view/helpers/tags/search_field.rb +2 -2
  84. data/lib/action_view/helpers/tags/select.rb +2 -3
  85. data/lib/action_view/helpers/tags/tel_field.rb +2 -2
  86. data/lib/action_view/helpers/tags/text_area.rb +2 -2
  87. data/lib/action_view/helpers/tags/text_field.rb +2 -2
  88. data/lib/action_view/helpers/tags/time_field.rb +2 -2
  89. data/lib/action_view/helpers/tags/time_select.rb +2 -2
  90. data/lib/action_view/helpers/tags/time_zone_select.rb +2 -2
  91. data/lib/action_view/helpers/tags/url_field.rb +2 -2
  92. data/lib/action_view/helpers/tags/week_field.rb +2 -2
  93. data/lib/action_view/helpers/text_helper.rb +8 -5
  94. data/lib/action_view/helpers/url_helper.rb +18 -6
  95. data/lib/action_view/lookup_context.rb +7 -1
  96. data/lib/action_view/path_set.rb +6 -0
  97. data/lib/action_view/renderer/abstract_renderer.rb +15 -0
  98. data/lib/action_view/renderer/partial_renderer.rb +14 -0
  99. data/lib/action_view/renderer/renderer.rb +6 -0
  100. data/lib/action_view/template.rb +3 -2
  101. data/lib/action_view/template/handlers/erb.rb +29 -3
  102. data/lib/action_view/template/resolver.rb +3 -3
  103. data/lib/action_view/test_case.rb +1 -0
  104. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +5 -5
  105. data/lib/action_view/vendor/html-scanner/html/selector.rb +8 -8
  106. 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 preceeding headers, and only report
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 begining
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 { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] }
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. Sessions typically
8
- # contain at most a user_id and flash message; both fit within the 4K cookie
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
- # If you have more than 4K of session data or don't want your data to be
13
- # visible to the user, pick another session store.
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
- # CookieOverflow is raised if you attempt to store more than 4K of data.
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
- # A message digest is included with the cookie to ensure data integrity:
18
- # a user cannot alter his +user_id+ without knowing the secret key
19
- # included in the hash. New apps are generated with a pregenerated secret
20
- # in config/environment.rb. Set your own for old apps you're upgrading.
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
- # Session options:
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
- # * <tt>:secret</tt>: An application-wide key string. It's important that
25
- # the secret is not vulnerable to a dictionary attack. Therefore, you
26
- # should choose a secret consisting of random numbers and letters and
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
- # secret: '449fe2e7daee471bffae2fd8dc02313d'
30
+ # Configure your session store in config/initializers/session_store.rb:
30
31
  #
31
- # * <tt>:digest</tt>: The message digest algorithm used to verify session
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
- # To generate a secret key for an existing application, run
36
- # "rake secret" and set the key in config/initializers/secret_token.rb.
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.signed
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, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n")
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
- traces = [
3
- ["Application Trace", @application_trace],
4
- ["Framework Trace", @framework_trace],
5
- ["Full Trace", @full_trace]
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.gsub('\\', '') %></code> failed because <%= reason.downcase %></li>
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"].capitalize if @request.parameters["controller"]%>#<%= @request.parameters["action"] %>
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
- # match '/login' => 'accounts#login', as: 'login'
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
- # match 'blog/show' => :list
108
- # match 'blog/delete' => :delete
109
- # match 'blog/edit/:id' => :edit
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
- # match '/articles/:year/:month/:day' => 'articles#find_by_id', constraints: {
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
- # match 'geocode/:postalcode' => :show, constraints: {
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
- # match 'geocode/:postalcode' => :show, constraints: {
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
- # match 'geocode/:postalcode' => :show, constraints: {
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 match modifier will raise an +ArgumentError+.
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
- # match "/stories" => redirect("/posts")
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
- # match "こんにちは" => "welcome#index"
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
- # match "/application.js" => Sprockets
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, use `get` in the router:\n\n" \
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 or an
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, v| k.respond_to?(:call) }
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] = method
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
- scope_options.each do |option|
704
- if value = options.delete(option)
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, value| name.is_a?(String) }
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 { |_path| decomposed_match(_path, options.dup) }
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
- [:resource, :resources].include? @scope[:scope_level]
1504
+ RESOURCE_SCOPES.include? @scope[:scope_level]
1492
1505
  end
1493
1506
 
1494
1507
  def resource_method_scope? #:nodoc:
1495
- [:collection, :member, :new].include? @scope[:scope_level]
1508
+ RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
1496
1509
  end
1497
1510
 
1498
1511
  def with_exclusive_scope