actionpack 3.0.0.beta3 → 3.0.0.beta4

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 (83) hide show
  1. data/CHANGELOG +19 -0
  2. data/lib/abstract_controller.rb +1 -1
  3. data/lib/abstract_controller/asset_paths.rb +9 -0
  4. data/lib/abstract_controller/base.rb +5 -13
  5. data/lib/abstract_controller/callbacks.rb +1 -1
  6. data/lib/abstract_controller/helpers.rb +0 -1
  7. data/lib/abstract_controller/layouts.rb +3 -3
  8. data/lib/abstract_controller/logger.rb +1 -1
  9. data/lib/abstract_controller/rendering.rb +1 -0
  10. data/lib/action_controller/base.rb +5 -1
  11. data/lib/action_controller/caching.rb +2 -3
  12. data/lib/action_controller/caching/actions.rb +1 -1
  13. data/lib/action_controller/caching/fragments.rb +1 -1
  14. data/lib/action_controller/caching/pages.rb +8 -8
  15. data/lib/action_controller/caching/sweeping.rb +1 -0
  16. data/lib/action_controller/deprecated/base.rb +10 -36
  17. data/lib/action_controller/metal.rb +45 -3
  18. data/lib/action_controller/metal/compatibility.rb +2 -2
  19. data/lib/action_controller/metal/helpers.rb +3 -3
  20. data/lib/action_controller/metal/http_authentication.rb +158 -0
  21. data/lib/action_controller/metal/instrumentation.rb +5 -5
  22. data/lib/action_controller/metal/rack_delegation.rb +4 -4
  23. data/lib/action_controller/metal/renderers.rb +3 -3
  24. data/lib/action_controller/metal/request_forgery_protection.rb +45 -74
  25. data/lib/action_controller/metal/responder.rb +1 -1
  26. data/lib/action_controller/metal/url_for.rb +8 -0
  27. data/lib/action_controller/railtie.rb +26 -39
  28. data/lib/action_controller/test_case.rb +147 -135
  29. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -0
  30. data/lib/action_dispatch.rb +0 -1
  31. data/lib/action_dispatch/http/parameters.rb +2 -1
  32. data/lib/action_dispatch/http/request.rb +19 -7
  33. data/lib/action_dispatch/http/response.rb +3 -33
  34. data/lib/action_dispatch/middleware/cookies.rb +44 -10
  35. data/lib/action_dispatch/middleware/flash.rb +11 -1
  36. data/lib/action_dispatch/middleware/params_parser.rb +3 -1
  37. data/lib/action_dispatch/middleware/session/abstract_store.rb +47 -83
  38. data/lib/action_dispatch/middleware/session/cookie_store.rb +19 -165
  39. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +2 -2
  40. data/lib/action_dispatch/middleware/show_exceptions.rb +18 -12
  41. data/lib/action_dispatch/middleware/stack.rb +17 -67
  42. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +1 -1
  43. data/lib/action_dispatch/railtie.rb +0 -2
  44. data/lib/action_dispatch/routing/deprecated_mapper.rb +1 -0
  45. data/lib/action_dispatch/routing/mapper.rb +89 -23
  46. data/lib/action_dispatch/routing/route_set.rb +22 -16
  47. data/lib/action_dispatch/routing/url_for.rb +1 -1
  48. data/lib/action_dispatch/testing/assertions/routing.rb +1 -0
  49. data/lib/action_dispatch/testing/assertions/selector.rb +11 -7
  50. data/lib/action_dispatch/testing/test_process.rb +3 -2
  51. data/lib/action_pack/version.rb +1 -1
  52. data/lib/action_view.rb +5 -1
  53. data/lib/action_view/base.rb +10 -4
  54. data/lib/action_view/helpers/active_model_helper.rb +1 -8
  55. data/lib/action_view/helpers/asset_tag_helper.rb +7 -4
  56. data/lib/action_view/helpers/cache_helper.rb +14 -14
  57. data/lib/action_view/helpers/capture_helper.rb +25 -6
  58. data/lib/action_view/helpers/date_helper.rb +33 -44
  59. data/lib/action_view/helpers/form_helper.rb +47 -27
  60. data/lib/action_view/helpers/form_options_helper.rb +26 -3
  61. data/lib/action_view/helpers/form_tag_helper.rb +8 -4
  62. data/lib/action_view/helpers/number_helper.rb +5 -2
  63. data/lib/action_view/helpers/prototype_helper.rb +1 -1
  64. data/lib/action_view/helpers/tag_helper.rb +1 -1
  65. data/lib/action_view/helpers/text_helper.rb +55 -46
  66. data/lib/action_view/helpers/translation_helper.rb +19 -8
  67. data/lib/action_view/helpers/url_helper.rb +2 -4
  68. data/lib/action_view/locale/en.yml +14 -14
  69. data/lib/action_view/lookup_context.rb +52 -22
  70. data/lib/action_view/paths.rb +1 -0
  71. data/lib/action_view/render/layouts.rb +3 -12
  72. data/lib/action_view/render/partials.rb +21 -10
  73. data/lib/action_view/render/rendering.rb +1 -1
  74. data/lib/action_view/template.rb +172 -26
  75. data/lib/action_view/template/error.rb +25 -27
  76. data/lib/action_view/template/handlers.rb +1 -1
  77. data/lib/action_view/template/handlers/erb.rb +92 -45
  78. data/lib/action_view/template/resolver.rb +4 -1
  79. data/lib/action_view/test_case.rb +105 -72
  80. data/lib/action_view/testing/resolvers.rb +43 -0
  81. metadata +62 -20
  82. data/lib/abstract_controller/assigns.rb +0 -21
  83. data/lib/action_dispatch/middleware/cascade.rb +0 -29
@@ -1,5 +1,6 @@
1
1
  require 'rack/session/abstract/id'
2
2
  require 'active_support/core_ext/object/blank'
3
+ require 'active_support/core_ext/object/to_query'
3
4
 
4
5
  module ActionController
5
6
  module TemplateAssertions
@@ -15,12 +16,12 @@ module ActionController
15
16
  @templates = Hash.new(0)
16
17
  @layouts = Hash.new(0)
17
18
 
18
- ActiveSupport::Notifications.subscribe("action_view.render_template") do |name, start, finish, id, payload|
19
+ ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
19
20
  path = payload[:layout]
20
21
  @layouts[path] += 1
21
22
  end
22
23
 
23
- ActiveSupport::Notifications.subscribe("action_view.render_template!") do |name, start, finish, id, payload|
24
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
24
25
  path = payload[:virtual_path]
25
26
  next unless path
26
27
  partial = path =~ /^.*\/_[^\/]*$/
@@ -35,7 +36,8 @@ module ActionController
35
36
  end
36
37
 
37
38
  def teardown_subscriptions
38
- ActiveSupport::Notifications.unsubscribe("action_view.render_template!")
39
+ ActiveSupport::Notifications.unsubscribe("render_template.action_view")
40
+ ActiveSupport::Notifications.unsubscribe("!render_template.action_view")
39
41
  end
40
42
 
41
43
  # Asserts that the request was rendered with the appropriate template file or partials
@@ -55,7 +57,8 @@ module ActionController
55
57
  validate_request!
56
58
 
57
59
  case options
58
- when NilClass, String
60
+ when NilClass, String, Symbol
61
+ options = options.to_s if Symbol === options
59
62
  rendered = @templates
60
63
  msg = build_message(message,
61
64
  "expecting <?> but rendering with <?>",
@@ -136,14 +139,16 @@ module ActionController
136
139
  end
137
140
  end
138
141
 
139
- params = self.request_parameters.dup
142
+ # Clear the combined params hash in case it was already referenced.
143
+ @env.delete("action_dispatch.request.parameters")
140
144
 
145
+ params = self.request_parameters.dup
141
146
  %w(controller action only_path).each do |k|
142
147
  params.delete(k)
143
148
  params.delete(k.to_sym)
144
149
  end
145
-
146
150
  data = params.to_query
151
+
147
152
  @env['CONTENT_LENGTH'] = data.length.to_s
148
153
  @env['rack.input'] = StringIO.new(data)
149
154
  end
@@ -152,6 +157,8 @@ module ActionController
152
157
  @formats = nil
153
158
  @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
154
159
  @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
160
+ @method = @request_method = nil
161
+ @fullpath = @ip = @remote_ip = nil
155
162
  @env['action_dispatch.request.query_parameters'] = {}
156
163
  end
157
164
  end
@@ -164,9 +171,7 @@ module ActionController
164
171
  @block = nil
165
172
  @length = 0
166
173
  @body = []
167
- @charset = nil
168
- @content_type = nil
169
-
174
+ @charset = @content_type = nil
170
175
  @request = @template = nil
171
176
  end
172
177
  end
@@ -281,165 +286,143 @@ module ActionController
281
286
  #
282
287
  # assert_redirected_to page_url(:title => 'foo')
283
288
  class TestCase < ActiveSupport::TestCase
284
- include ActionDispatch::TestProcess
285
- include ActionController::TemplateAssertions
289
+ module Behavior
290
+ extend ActiveSupport::Concern
291
+ include ActionDispatch::TestProcess
286
292
 
287
- attr_reader :response, :request
293
+ attr_reader :response, :request
288
294
 
289
- # Executes a request simulating GET HTTP method and set/volley the response
290
- def get(action, parameters = nil, session = nil, flash = nil)
291
- process(action, parameters, session, flash, "GET")
292
- end
295
+ module ClassMethods
293
296
 
294
- # Executes a request simulating POST HTTP method and set/volley the response
295
- def post(action, parameters = nil, session = nil, flash = nil)
296
- process(action, parameters, session, flash, "POST")
297
- end
297
+ # Sets the controller class name. Useful if the name can't be inferred from test class.
298
+ # Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
299
+ def tests(controller_class)
300
+ self.controller_class = controller_class
301
+ end
302
+
303
+ def controller_class=(new_class)
304
+ prepare_controller_class(new_class) if new_class
305
+ write_inheritable_attribute(:controller_class, new_class)
306
+ end
298
307
 
299
- # Executes a request simulating PUT HTTP method and set/volley the response
300
- def put(action, parameters = nil, session = nil, flash = nil)
301
- process(action, parameters, session, flash, "PUT")
302
- end
308
+ def controller_class
309
+ if current_controller_class = read_inheritable_attribute(:controller_class)
310
+ current_controller_class
311
+ else
312
+ self.controller_class = determine_default_controller_class(name)
313
+ end
314
+ end
303
315
 
304
- # Executes a request simulating DELETE HTTP method and set/volley the response
305
- def delete(action, parameters = nil, session = nil, flash = nil)
306
- process(action, parameters, session, flash, "DELETE")
307
- end
316
+ def determine_default_controller_class(name)
317
+ name.sub(/Test$/, '').constantize
318
+ rescue NameError
319
+ nil
320
+ end
308
321
 
309
- # Executes a request simulating HEAD HTTP method and set/volley the response
310
- def head(action, parameters = nil, session = nil, flash = nil)
311
- process(action, parameters, session, flash, "HEAD")
312
- end
322
+ def prepare_controller_class(new_class)
323
+ new_class.send :include, ActionController::TestCase::RaiseActionExceptions
324
+ end
313
325
 
314
- def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
315
- @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
316
- @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
317
- returning __send__(request_method, action, parameters, session, flash) do
318
- @request.env.delete 'HTTP_X_REQUESTED_WITH'
319
- @request.env.delete 'HTTP_ACCEPT'
320
326
  end
321
- end
322
- alias xhr :xml_http_request
323
-
324
- def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
325
- # Sanity check for required instance variables so we can give an
326
- # understandable error message.
327
- %w(@routes @controller @request @response).each do |iv_name|
328
- if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
329
- raise "#{iv_name} is nil: make sure you set it in your test's setup method."
330
- end
327
+
328
+ # Executes a request simulating GET HTTP method and set/volley the response
329
+ def get(action, parameters = nil, session = nil, flash = nil)
330
+ process(action, parameters, session, flash, "GET")
331
331
  end
332
332
 
333
- @request.recycle!
334
- @response.recycle!
335
- @controller.response_body = nil
336
- @controller.formats = nil
337
- @controller.params = nil
338
-
339
- @html_document = nil
340
- @request.env['REQUEST_METHOD'] = http_method
341
-
342
- parameters ||= {}
343
- @request.assign_parameters(@routes, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
344
-
345
- @request.session = ActionController::TestSession.new(session) unless session.nil?
346
- @request.session["flash"] = @request.flash.update(flash || {})
347
- @request.session["flash"].sweep
348
-
349
- @controller.request = @request
350
- @controller.params.merge!(parameters)
351
- build_request_uri(action, parameters)
352
- Base.class_eval { include Testing }
353
- @controller.process_with_new_base_test(@request, @response)
354
- @request.session.delete('flash') if @request.session['flash'].blank?
355
- @response
356
- end
333
+ # Executes a request simulating POST HTTP method and set/volley the response
334
+ def post(action, parameters = nil, session = nil, flash = nil)
335
+ process(action, parameters, session, flash, "POST")
336
+ end
357
337
 
358
- include ActionDispatch::Assertions
338
+ # Executes a request simulating PUT HTTP method and set/volley the response
339
+ def put(action, parameters = nil, session = nil, flash = nil)
340
+ process(action, parameters, session, flash, "PUT")
341
+ end
359
342
 
360
- # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
361
- # (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
362
- # rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
363
- # than 0.0.0.0.
364
- #
365
- # The exception is stored in the exception accessor for further inspection.
366
- module RaiseActionExceptions
367
- def self.included(base)
368
- base.class_eval do
369
- attr_accessor :exception
370
- protected :exception, :exception=
371
- end
343
+ # Executes a request simulating DELETE HTTP method and set/volley the response
344
+ def delete(action, parameters = nil, session = nil, flash = nil)
345
+ process(action, parameters, session, flash, "DELETE")
372
346
  end
373
347
 
374
- protected
375
- def rescue_action_without_handler(e)
376
- self.exception = e
348
+ # Executes a request simulating HEAD HTTP method and set/volley the response
349
+ def head(action, parameters = nil, session = nil, flash = nil)
350
+ process(action, parameters, session, flash, "HEAD")
351
+ end
377
352
 
378
- if request.remote_addr == "0.0.0.0"
379
- raise(e)
380
- else
381
- super(e)
353
+ def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
354
+ @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
355
+ @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
356
+ returning __send__(request_method, action, parameters, session, flash) do
357
+ @request.env.delete 'HTTP_X_REQUESTED_WITH'
358
+ @request.env.delete 'HTTP_ACCEPT'
359
+ end
360
+ end
361
+ alias xhr :xml_http_request
362
+
363
+ def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
364
+ # Sanity check for required instance variables so we can give an
365
+ # understandable error message.
366
+ %w(@routes @controller @request @response).each do |iv_name|
367
+ if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
368
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
382
369
  end
383
370
  end
384
- end
385
371
 
386
- setup :setup_controller_request_and_response
372
+ @request.recycle!
373
+ @response.recycle!
374
+ @controller.response_body = nil
375
+ @controller.formats = nil
376
+ @controller.params = nil
387
377
 
388
- @@controller_class = nil
378
+ @html_document = nil
379
+ @request.env['REQUEST_METHOD'] = http_method
389
380
 
390
- class << self
391
- # Sets the controller class name. Useful if the name can't be inferred from test class.
392
- # Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>.
393
- def tests(controller_class)
394
- self.controller_class = controller_class
395
- end
381
+ parameters ||= {}
382
+ @request.assign_parameters(@routes, @controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters)
396
383
 
397
- def controller_class=(new_class)
398
- prepare_controller_class(new_class) if new_class
399
- write_inheritable_attribute(:controller_class, new_class)
400
- end
384
+ @request.session = ActionController::TestSession.new(session) unless session.nil?
385
+ @request.session["flash"] = @request.flash.update(flash || {})
386
+ @request.session["flash"].sweep
401
387
 
402
- def controller_class
403
- if current_controller_class = read_inheritable_attribute(:controller_class)
404
- current_controller_class
405
- else
406
- self.controller_class = determine_default_controller_class(name)
407
- end
388
+ @controller.request = @request
389
+ @controller.params.merge!(parameters)
390
+ build_request_uri(action, parameters)
391
+ Base.class_eval { include Testing }
392
+ @controller.process_with_new_base_test(@request, @response)
393
+ @request.session.delete('flash') if @request.session['flash'].blank?
394
+ @response
408
395
  end
409
396
 
410
- def determine_default_controller_class(name)
411
- name.sub(/Test$/, '').constantize
412
- rescue NameError
413
- nil
414
- end
397
+ def setup_controller_request_and_response
398
+ @request = TestRequest.new
399
+ @response = TestResponse.new
415
400
 
416
- def prepare_controller_class(new_class)
417
- new_class.send :include, RaiseActionExceptions
418
- end
419
- end
401
+ if klass = self.class.controller_class
402
+ @controller ||= klass.new rescue nil
403
+ end
420
404
 
421
- def setup_controller_request_and_response
422
- @request = TestRequest.new
423
- @response = TestResponse.new
405
+ @request.env.delete('PATH_INFO')
424
406
 
425
- if klass = self.class.controller_class
426
- @controller ||= klass.new rescue nil
407
+ if @controller
408
+ @controller.request = @request
409
+ @controller.params = {}
410
+ end
427
411
  end
428
412
 
429
- @request.env.delete('PATH_INFO')
430
-
431
- if @controller
432
- @controller.request = @request
433
- @controller.params = {}
413
+ # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
414
+ def rescue_action_in_public!
415
+ @request.remote_addr = '208.77.188.166' # example.com
434
416
  end
435
- end
436
417
 
437
- # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local
438
- def rescue_action_in_public!
439
- @request.remote_addr = '208.77.188.166' # example.com
440
- end
418
+ included do
419
+ include ActionController::TemplateAssertions
420
+ include ActionDispatch::Assertions
421
+ setup :setup_controller_request_and_response
422
+ end
441
423
 
442
424
  private
425
+
443
426
  def build_request_uri(action, parameters)
444
427
  unless @request.env["PATH_INFO"]
445
428
  options = @controller.__send__(:url_options).merge(parameters)
@@ -457,4 +440,33 @@ module ActionController
457
440
  end
458
441
  end
459
442
  end
443
+
444
+ # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline
445
+ # (bystepping the regular exception handling from rescue_action). If the request.remote_addr is anything else, the regular
446
+ # rescue_action process takes place. This means you can test your rescue_action code by setting remote_addr to something else
447
+ # than 0.0.0.0.
448
+ #
449
+ # The exception is stored in the exception accessor for further inspection.
450
+ module RaiseActionExceptions
451
+ def self.included(base)
452
+ base.class_eval do
453
+ attr_accessor :exception
454
+ protected :exception, :exception=
455
+ end
456
+ end
457
+
458
+ protected
459
+ def rescue_action_without_handler(e)
460
+ self.exception = e
461
+
462
+ if request.remote_addr == "0.0.0.0"
463
+ raise(e)
464
+ else
465
+ super(e)
466
+ end
467
+ end
468
+ end
469
+
470
+ include Behavior
471
+ end
460
472
  end
@@ -23,6 +23,7 @@ module HTML #:nodoc:
23
23
 
24
24
  # Create a new Tokenizer for the given text.
25
25
  def initialize(text)
26
+ text.encode! if text.encoding_aware?
26
27
  @scanner = StringScanner.new(text)
27
28
  @position = 0
28
29
  @line = 0
@@ -43,7 +43,6 @@ module ActionDispatch
43
43
 
44
44
  autoload_under 'middleware' do
45
45
  autoload :Callbacks
46
- autoload :Cascade
47
46
  autoload :Cookies
48
47
  autoload :Flash
49
48
  autoload :Head
@@ -1,4 +1,5 @@
1
1
  require 'active_support/core_ext/hash/keys'
2
+ require 'active_support/core_ext/hash/indifferent_access'
2
3
 
3
4
  module ActionDispatch
4
5
  module Http
@@ -46,4 +47,4 @@ module ActionDispatch
46
47
  end
47
48
  end
48
49
  end
49
- end
50
+ end
@@ -52,9 +52,11 @@ module ActionDispatch
52
52
  # the application should use), this \method returns the overridden
53
53
  # value, not the original.
54
54
  def request_method
55
- method = env["REQUEST_METHOD"]
56
- HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
57
- method
55
+ @request_method ||= begin
56
+ method = env["REQUEST_METHOD"]
57
+ HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
58
+ method
59
+ end
58
60
  end
59
61
 
60
62
  # Returns a symbol form of the #request_method
@@ -66,9 +68,11 @@ module ActionDispatch
66
68
  # even if it was overridden by middleware. See #request_method for
67
69
  # more information.
68
70
  def method
69
- method = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
70
- HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
71
- method
71
+ @method ||= begin
72
+ method = env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']
73
+ HTTP_METHOD_LOOKUP[method] || raise(ActionController::UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
74
+ method
75
+ end
72
76
  end
73
77
 
74
78
  # Returns a symbol form of the #method
@@ -113,6 +117,10 @@ module ActionDispatch
113
117
  Http::Headers.new(@env)
114
118
  end
115
119
 
120
+ def fullpath
121
+ @fullpath ||= super
122
+ end
123
+
116
124
  def forgery_whitelisted?
117
125
  get? || xhr? || content_mime_type.nil? || !content_mime_type.verify_request?
118
126
  end
@@ -134,6 +142,10 @@ module ActionDispatch
134
142
  end
135
143
  alias :xhr? :xml_http_request?
136
144
 
145
+ def ip
146
+ @ip ||= super
147
+ end
148
+
137
149
  # Which IP addresses are "trusted proxies" that can be stripped from
138
150
  # the right-hand-side of X-Forwarded-For
139
151
  TRUSTED_PROXIES = /^127\.0\.0\.1$|^(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\./i
@@ -145,7 +157,7 @@ module ActionDispatch
145
157
  # delimited list in the case of multiple chained proxies; the last
146
158
  # address which is not trusted is the originating IP.
147
159
  def remote_ip
148
- (@env["action_dispatch.remote_ip"] || ip).to_s
160
+ @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
149
161
  end
150
162
 
151
163
  # Returns the lowercase name of the HTTP server software.