actionpack 4.1.0.beta2 → 4.1.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -10
  3. data/MIT-LICENSE +1 -1
  4. data/lib/abstract_controller/rendering.rb +4 -3
  5. data/lib/action_controller/log_subscriber.rb +9 -0
  6. data/lib/action_controller/metal/head.rb +1 -1
  7. data/lib/action_controller/metal/live.rb +3 -1
  8. data/lib/action_controller/metal/mime_responds.rb +66 -27
  9. data/lib/action_controller/metal/params_wrapper.rb +11 -4
  10. data/lib/action_controller/metal/rack_delegation.rb +2 -2
  11. data/lib/action_controller/metal/renderers.rb +1 -1
  12. data/lib/action_controller/metal/rendering.rb +38 -8
  13. data/lib/action_controller/metal/strong_parameters.rb +26 -14
  14. data/lib/action_controller/railtie.rb +1 -0
  15. data/lib/action_controller/test_case.rb +3 -0
  16. data/lib/action_dispatch.rb +5 -7
  17. data/lib/action_dispatch/http/filter_redirect.rb +5 -4
  18. data/lib/action_dispatch/http/mime_negotiation.rb +5 -3
  19. data/lib/action_dispatch/http/mime_type.rb +1 -1
  20. data/lib/action_dispatch/http/response.rb +14 -2
  21. data/lib/action_dispatch/journey/formatter.rb +2 -2
  22. data/lib/action_dispatch/journey/router.rb +1 -7
  23. data/lib/action_dispatch/journey/visitors.rb +24 -4
  24. data/lib/action_dispatch/middleware/cookies.rb +85 -20
  25. data/lib/action_dispatch/middleware/flash.rb +20 -7
  26. data/lib/action_dispatch/middleware/reloader.rb +11 -2
  27. data/lib/action_dispatch/middleware/remote_ip.rb +2 -2
  28. data/lib/action_dispatch/middleware/static.rb +3 -3
  29. data/lib/action_dispatch/railtie.rb +2 -0
  30. data/lib/action_dispatch/request/utils.rb +15 -4
  31. data/lib/action_dispatch/routing/inspector.rb +4 -4
  32. data/lib/action_dispatch/routing/mapper.rb +27 -9
  33. data/lib/action_dispatch/routing/redirection.rb +18 -8
  34. data/lib/action_dispatch/routing/route_set.rb +24 -30
  35. data/lib/action_dispatch/testing/assertions/routing.rb +1 -1
  36. data/lib/action_dispatch/testing/integration.rb +2 -2
  37. data/lib/action_pack.rb +1 -1
  38. data/lib/action_pack/version.rb +1 -1
  39. metadata +7 -7
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+
1
3
  module ActionDispatch
2
4
  class Request < Rack::Request
3
5
  # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
@@ -50,13 +52,14 @@ module ActionDispatch
50
52
  end
51
53
 
52
54
  def []=(k, v)
55
+ k = k.to_s
53
56
  @flash[k] = v
54
57
  @flash.discard(k)
55
58
  v
56
59
  end
57
60
 
58
61
  def [](k)
59
- @flash[k]
62
+ @flash[k.to_s]
60
63
  end
61
64
 
62
65
  # Convenience accessor for <tt>flash.now[:alert]=</tt>.
@@ -92,8 +95,8 @@ module ActionDispatch
92
95
  end
93
96
 
94
97
  def initialize(flashes = {}, discard = []) #:nodoc:
95
- @discard = Set.new(discard)
96
- @flashes = flashes
98
+ @discard = Set.new(stringify_array(discard))
99
+ @flashes = flashes.stringify_keys
97
100
  @now = nil
98
101
  end
99
102
 
@@ -106,17 +109,18 @@ module ActionDispatch
106
109
  end
107
110
 
108
111
  def []=(k, v)
112
+ k = k.to_s
109
113
  @discard.delete k
110
114
  @flashes[k] = v
111
115
  end
112
116
 
113
117
  def [](k)
114
- @flashes[k]
118
+ @flashes[k.to_s]
115
119
  end
116
120
 
117
121
  def update(h) #:nodoc:
118
- @discard.subtract h.keys
119
- @flashes.update h
122
+ @discard.subtract stringify_array(h.keys)
123
+ @flashes.update h.stringify_keys
120
124
  self
121
125
  end
122
126
 
@@ -129,6 +133,7 @@ module ActionDispatch
129
133
  end
130
134
 
131
135
  def delete(key)
136
+ key = key.to_s
132
137
  @discard.delete key
133
138
  @flashes.delete key
134
139
  self
@@ -155,7 +160,7 @@ module ActionDispatch
155
160
 
156
161
  def replace(h) #:nodoc:
157
162
  @discard.clear
158
- @flashes.replace h
163
+ @flashes.replace h.stringify_keys
159
164
  self
160
165
  end
161
166
 
@@ -186,6 +191,7 @@ module ActionDispatch
186
191
  # flash.keep # keeps the entire flash
187
192
  # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
188
193
  def keep(k = nil)
194
+ k = k.to_s if k
189
195
  @discard.subtract Array(k || keys)
190
196
  k ? self[k] : self
191
197
  end
@@ -195,6 +201,7 @@ module ActionDispatch
195
201
  # flash.discard # discard the entire flash at the end of the current action
196
202
  # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
197
203
  def discard(k = nil)
204
+ k = k.to_s if k
198
205
  @discard.merge Array(k || keys)
199
206
  k ? self[k] : self
200
207
  end
@@ -231,6 +238,12 @@ module ActionDispatch
231
238
  def now_is_loaded?
232
239
  @now
233
240
  end
241
+
242
+ def stringify_array(array)
243
+ array.map do |item|
244
+ item.kind_of?(Symbol) ? item.to_s : item
245
+ end
246
+ end
234
247
  end
235
248
 
236
249
  def initialize(app)
@@ -1,3 +1,5 @@
1
+ require 'active_support/deprecation/reporting'
2
+
1
3
  module ActionDispatch
2
4
  # ActionDispatch::Reloader provides prepare and cleanup callbacks,
3
5
  # intended to assist with code reloading during development.
@@ -25,19 +27,26 @@ module ActionDispatch
25
27
  #
26
28
  class Reloader
27
29
  include ActiveSupport::Callbacks
30
+ include ActiveSupport::Deprecation::Reporting
28
31
 
29
- define_callbacks :prepare, :scope => :name
30
- define_callbacks :cleanup, :scope => :name
32
+ define_callbacks :prepare
33
+ define_callbacks :cleanup
31
34
 
32
35
  # Add a prepare callback. Prepare callbacks are run before each request, prior
33
36
  # to ActionDispatch::Callback's before callbacks.
34
37
  def self.to_prepare(*args, &block)
38
+ unless block_given?
39
+ warn "to_prepare without a block is deprecated. Please use a block"
40
+ end
35
41
  set_callback(:prepare, *args, &block)
36
42
  end
37
43
 
38
44
  # Add a cleanup callback. Cleanup callbacks are run after each request is
39
45
  # complete (after #close is called on the response body).
40
46
  def self.to_cleanup(*args, &block)
47
+ unless block_given?
48
+ warn "to_cleanup without a block is deprecated. Please use a block"
49
+ end
41
50
  set_callback(:cleanup, *args, &block)
42
51
  end
43
52
 
@@ -47,12 +47,12 @@ module ActionDispatch
47
47
  # clients (like WAP devices), or behind proxies that set headers in an
48
48
  # incorrect or confusing way (like AWS ELB).
49
49
  #
50
- # The +custom_trusted+ argument can take a regex, which will be used
50
+ # The +custom_proxies+ argument can take a regex, which will be used
51
51
  # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition
52
52
  # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the
53
53
  # middle (or at the beginning) of the X-Forwarded-For list, with your proxy
54
54
  # servers after it. If your proxies aren't removed, pass them in via the
55
- # +custom_trusted+ parameter. That way, the middleware will ignore those
55
+ # +custom_proxies+ parameter. That way, the middleware will ignore those
56
56
  # IP addresses, and return the one that you want.
57
57
  def initialize(app, check_ip_spoofing = true, custom_proxies = nil)
58
58
  @app = app
@@ -11,9 +11,10 @@ module ActionDispatch
11
11
  end
12
12
 
13
13
  def match?(path)
14
- path = path.dup
14
+ path = unescape_path(path)
15
+ return false unless path.valid_encoding?
15
16
 
16
- full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path)))
17
+ full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(path))
17
18
  paths = "#{full_path}#{ext}"
18
19
 
19
20
  matches = Dir[paths]
@@ -40,7 +41,6 @@ module ActionDispatch
40
41
  end
41
42
 
42
43
  def escape_glob_chars(path)
43
- path.force_encoding('binary') if path.respond_to? :force_encoding
44
44
  path.gsub(/[*?{}\[\]]/, "\\\\\\&")
45
45
  end
46
46
  end
@@ -16,6 +16,7 @@ module ActionDispatch
16
16
  config.action_dispatch.signed_cookie_salt = 'signed cookie'
17
17
  config.action_dispatch.encrypted_cookie_salt = 'encrypted cookie'
18
18
  config.action_dispatch.encrypted_signed_cookie_salt = 'signed encrypted cookie'
19
+ config.action_dispatch.perform_deep_munge = true
19
20
 
20
21
  config.action_dispatch.default_headers = {
21
22
  'X-Frame-Options' => 'SAMEORIGIN',
@@ -28,6 +29,7 @@ module ActionDispatch
28
29
  initializer "action_dispatch.configure" do |app|
29
30
  ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
30
31
  ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
32
+ ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
31
33
  ActionDispatch::Response.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
32
34
  ActionDispatch::Response.default_headers = app.config.action_dispatch.default_headers
33
35
 
@@ -1,18 +1,29 @@
1
1
  module ActionDispatch
2
2
  class Request < Rack::Request
3
3
  class Utils # :nodoc:
4
+
5
+ mattr_accessor :perform_deep_munge
6
+ self.perform_deep_munge = true
7
+
4
8
  class << self
5
9
  # Remove nils from the params hash
6
- def deep_munge(hash)
10
+ def deep_munge(hash, keys = [])
11
+ return hash unless perform_deep_munge
12
+
7
13
  hash.each do |k, v|
14
+ keys << k
8
15
  case v
9
16
  when Array
10
- v.grep(Hash) { |x| deep_munge(x) }
17
+ v.grep(Hash) { |x| deep_munge(x, keys) }
11
18
  v.compact!
12
- hash[k] = nil if v.empty?
19
+ if v.empty?
20
+ hash[k] = nil
21
+ ActiveSupport::Notifications.instrument("deep_munge.action_controller", keys: keys)
22
+ end
13
23
  when Hash
14
- deep_munge(v)
24
+ deep_munge(v, keys)
15
25
  end
26
+ keys.pop
16
27
  end
17
28
 
18
29
  hash
@@ -69,7 +69,7 @@ module ActionDispatch
69
69
  end
70
70
 
71
71
  def internal?
72
- controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}}
72
+ controller.to_s =~ %r{\Arails/(info|mailers|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}\z}
73
73
  end
74
74
 
75
75
  def engine?
@@ -194,9 +194,9 @@ module ActionDispatch
194
194
  end
195
195
 
196
196
  def widths(routes)
197
- [routes.map { |r| r[:name].length }.max,
198
- routes.map { |r| r[:verb].length }.max,
199
- routes.map { |r| r[:path].length }.max]
197
+ [routes.map { |r| r[:name].length }.max || 0,
198
+ routes.map { |r| r[:verb].length }.max || 0,
199
+ routes.map { |r| r[:path].length }.max || 0]
200
200
  end
201
201
  end
202
202
 
@@ -3,6 +3,7 @@ require 'active_support/core_ext/hash/reverse_merge'
3
3
  require 'active_support/core_ext/hash/slice'
4
4
  require 'active_support/core_ext/enumerable'
5
5
  require 'active_support/core_ext/array/extract_options'
6
+ require 'active_support/core_ext/module/remove_method'
6
7
  require 'active_support/inflector'
7
8
  require 'action_dispatch/routing/redirection'
8
9
 
@@ -217,8 +218,12 @@ module ActionDispatch
217
218
  controller ||= default_controller
218
219
  action ||= default_action
219
220
 
220
- unless controller.is_a?(Regexp)
221
- controller = [@scope[:module], controller].compact.join("/").presence
221
+ if @scope[:module] && !controller.is_a?(Regexp)
222
+ if controller =~ %r{\A/}
223
+ controller = controller[1..-1]
224
+ else
225
+ controller = [@scope[:module], controller].compact.join("/").presence
226
+ end
222
227
  end
223
228
 
224
229
  if controller.is_a?(String) && controller =~ %r{\A/}
@@ -546,11 +551,11 @@ module ActionDispatch
546
551
  _routes = @set
547
552
  app.routes.define_mounted_helper(name)
548
553
  app.routes.singleton_class.class_eval do
549
- define_method :mounted? do
554
+ redefine_method :mounted? do
550
555
  true
551
556
  end
552
557
 
553
- define_method :_generate_prefix do |options|
558
+ redefine_method :_generate_prefix do |options|
554
559
  prefix_options = options.slice(*_route.segment_keys)
555
560
  # we must actually delete prefix segment keys to avoid passing them to next url_for
556
561
  _route.segment_keys.each { |k| options.delete(k) }
@@ -702,6 +707,10 @@ module ActionDispatch
702
707
  options[:path] = args.flatten.join('/') if args.any?
703
708
  options[:constraints] ||= {}
704
709
 
710
+ unless shallow?
711
+ options[:shallow_path] = options[:path] if args.any?
712
+ end
713
+
705
714
  if options[:constraints].is_a?(Hash)
706
715
  defaults = options[:constraints].select do
707
716
  |k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
@@ -1364,7 +1373,7 @@ module ActionDispatch
1364
1373
  end
1365
1374
 
1366
1375
  def shallow
1367
- scope(:shallow => true, :shallow_path => @scope[:path]) do
1376
+ scope(:shallow => true) do
1368
1377
  yield
1369
1378
  end
1370
1379
  end
@@ -1405,6 +1414,7 @@ module ActionDispatch
1405
1414
  path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
1406
1415
  if using_match_shorthand?(path_without_format, route_options)
1407
1416
  route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
1417
+ route_options[:to].tr!("-", "_")
1408
1418
  end
1409
1419
 
1410
1420
  decomposed_match(_path, route_options)
@@ -1435,8 +1445,8 @@ module ActionDispatch
1435
1445
  path = path_for_action(action, options.delete(:path))
1436
1446
  action = action.to_s.dup
1437
1447
 
1438
- if action =~ /^[\w\/]+$/
1439
- options[:action] ||= action unless action.include?("/")
1448
+ if action =~ /^[\w\-\/]+$/
1449
+ options[:action] ||= action.tr('-', '_') unless action.include?("/")
1440
1450
  else
1441
1451
  action = nil
1442
1452
  end
@@ -1484,6 +1494,13 @@ module ActionDispatch
1484
1494
  return true
1485
1495
  end
1486
1496
 
1497
+ if options.delete(:shallow)
1498
+ shallow do
1499
+ send(method, resources.pop, options, &block)
1500
+ end
1501
+ return true
1502
+ end
1503
+
1487
1504
  if resource_scope?
1488
1505
  nested { send(method, resources.pop, options, &block) }
1489
1506
  return true
@@ -1601,10 +1618,11 @@ module ActionDispatch
1601
1618
 
1602
1619
  def prefix_name_for_action(as, action) #:nodoc:
1603
1620
  if as
1604
- as.to_s
1621
+ prefix = as
1605
1622
  elsif !canonical_action?(action, @scope[:scope_level])
1606
- action.to_s
1623
+ prefix = action
1607
1624
  end
1625
+ prefix.to_s.tr('-', '_') if prefix
1608
1626
  end
1609
1627
 
1610
1628
  def name_for_action(as, action) #:nodoc:
@@ -26,14 +26,19 @@ module ActionDispatch
26
26
  end
27
27
 
28
28
  uri = URI.parse(path(req.symbolized_path_parameters, req))
29
+
30
+ unless uri.host
31
+ if relative_path?(uri.path)
32
+ uri.path = "#{req.script_name}/#{uri.path}"
33
+ elsif uri.path.empty?
34
+ uri.path = req.script_name.empty? ? "/" : req.script_name
35
+ end
36
+ end
37
+
29
38
  uri.scheme ||= req.scheme
30
39
  uri.host ||= req.host
31
40
  uri.port ||= req.port unless req.standard_port?
32
41
 
33
- if relative_path?(uri.path)
34
- uri.path = "#{req.script_name}/#{uri.path}"
35
- end
36
-
37
42
  body = %(<html><body>You are being <a href="#{ERB::Util.h(uri.to_s)}">redirected</a>.</body></html>)
38
43
 
39
44
  headers = {
@@ -112,11 +117,16 @@ module ActionDispatch
112
117
  url_options[:path] = (url_options[:path] % escape_path(params))
113
118
  end
114
119
 
115
- if relative_path?(url_options[:path])
116
- url_options[:path] = "/#{url_options[:path]}"
117
- url_options[:script_name] = request.script_name
120
+ unless options[:host] || options[:domain]
121
+ if relative_path?(url_options[:path])
122
+ url_options[:path] = "/#{url_options[:path]}"
123
+ url_options[:script_name] = request.script_name
124
+ elsif url_options[:path].empty?
125
+ url_options[:path] = request.script_name.empty? ? "/" : ""
126
+ url_options[:script_name] = request.script_name
127
+ end
118
128
  end
119
-
129
+
120
130
  ActionDispatch::Http::URL.url_for url_options
121
131
  end
122
132
 
@@ -163,9 +163,10 @@ module ActionDispatch
163
163
 
164
164
  def initialize(route, options)
165
165
  super
166
- @path_parts = @route.required_parts
167
- @arg_size = @path_parts.size
168
- @string_route = @route.optimized_path
166
+ @klass = Journey::Router::Utils
167
+ @required_parts = @route.required_parts
168
+ @arg_size = @required_parts.size
169
+ @optimized_path = @route.optimized_path
169
170
  end
170
171
 
171
172
  def call(t, args)
@@ -182,43 +183,36 @@ module ActionDispatch
182
183
  private
183
184
 
184
185
  def optimized_helper(args)
185
- path = @string_route.dup
186
- klass = Journey::Router::Utils
186
+ params = Hash[parameterize_args(args)]
187
+ missing_keys = missing_keys(params)
187
188
 
188
- @path_parts.zip(args) do |part, arg|
189
- parameterized_arg = arg.to_param
189
+ unless missing_keys.empty?
190
+ raise_generation_error(params, missing_keys)
191
+ end
190
192
 
191
- if parameterized_arg.nil? || parameterized_arg.empty?
192
- raise_generation_error(args)
193
- end
193
+ @optimized_path.map{ |segment| replace_segment(params, segment) }.join
194
+ end
194
195
 
195
- # Replace each route parameter
196
- # e.g. :id for regular parameter or *path for globbing
197
- # with ruby string interpolation code
198
- path.gsub!(/(\*|:)#{part}/, klass.escape_fragment(parameterized_arg))
199
- end
200
- path
196
+ def replace_segment(params, segment)
197
+ Symbol === segment ? @klass.escape_fragment(params[segment]) : segment
201
198
  end
202
199
 
203
200
  def optimize_routes_generation?(t)
204
201
  t.send(:optimize_routes_generation?)
205
202
  end
206
203
 
207
- def raise_generation_error(args)
208
- parts, missing_keys = [], []
209
-
210
- @path_parts.zip(args) do |part, arg|
211
- parameterized_arg = arg.to_param
212
-
213
- if parameterized_arg.nil? || parameterized_arg.empty?
214
- missing_keys << part
215
- end
204
+ def parameterize_args(args)
205
+ @required_parts.zip(args.map(&:to_param))
206
+ end
216
207
 
217
- parts << [part, arg]
218
- end
208
+ def missing_keys(args)
209
+ args.select{ |part, arg| arg.nil? || arg.empty? }.keys
210
+ end
219
211
 
220
- message = "No route matches #{Hash[parts].inspect}"
221
- message << " missing required keys: #{missing_keys.inspect}"
212
+ def raise_generation_error(args, missing_keys)
213
+ constraints = Hash[@route.requirements.merge(args).sort]
214
+ message = "No route matches #{constraints.inspect}"
215
+ message << " missing required keys: #{missing_keys.sort.inspect}"
222
216
 
223
217
  raise ActionController::UrlGenerationError, message
224
218
  end
@@ -226,7 +220,7 @@ module ActionDispatch
226
220
 
227
221
  def initialize(route, options)
228
222
  @options = options
229
- @segment_keys = route.segment_keys
223
+ @segment_keys = route.segment_keys.uniq
230
224
  @route = route
231
225
  end
232
226