actionpack 5.2.1 → 7.0.2.4

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -220
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -6
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +24 -4
  7. data/lib/abstract_controller/caching/fragments.rb +8 -24
  8. data/lib/abstract_controller/caching.rb +2 -2
  9. data/lib/abstract_controller/callbacks.rb +34 -8
  10. data/lib/abstract_controller/collector.rb +5 -4
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +107 -90
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +19 -1
  15. data/lib/abstract_controller/rendering.rb +9 -9
  16. data/lib/abstract_controller/translation.rb +12 -5
  17. data/lib/abstract_controller/url_for.rb +4 -6
  18. data/lib/abstract_controller.rb +2 -0
  19. data/lib/action_controller/api.rb +5 -4
  20. data/lib/action_controller/base.rb +6 -9
  21. data/lib/action_controller/caching.rb +1 -3
  22. data/lib/action_controller/log_subscriber.rb +13 -9
  23. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  24. data/lib/action_controller/metal/conditional_get.rb +57 -6
  25. data/lib/action_controller/metal/content_security_policy.rb +2 -3
  26. data/lib/action_controller/metal/cookies.rb +4 -2
  27. data/lib/action_controller/metal/data_streaming.rb +9 -18
  28. data/lib/action_controller/metal/default_headers.rb +17 -0
  29. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  30. data/lib/action_controller/metal/exceptions.rb +55 -12
  31. data/lib/action_controller/metal/flash.rb +10 -6
  32. data/lib/action_controller/metal/head.rb +7 -4
  33. data/lib/action_controller/metal/helpers.rb +15 -6
  34. data/lib/action_controller/metal/http_authentication.rb +41 -39
  35. data/lib/action_controller/metal/implicit_render.rb +5 -15
  36. data/lib/action_controller/metal/instrumentation.rb +59 -55
  37. data/lib/action_controller/metal/live.rb +80 -33
  38. data/lib/action_controller/metal/logging.rb +20 -0
  39. data/lib/action_controller/metal/mime_responds.rb +22 -7
  40. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  41. data/lib/action_controller/metal/params_wrapper.rb +50 -31
  42. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  43. data/lib/action_controller/metal/redirecting.rb +93 -23
  44. data/lib/action_controller/metal/renderers.rb +4 -4
  45. data/lib/action_controller/metal/rendering.rb +14 -9
  46. data/lib/action_controller/metal/request_forgery_protection.rb +160 -58
  47. data/lib/action_controller/metal/rescue.rb +2 -2
  48. data/lib/action_controller/metal/streaming.rb +1 -4
  49. data/lib/action_controller/metal/strong_parameters.rb +236 -88
  50. data/lib/action_controller/metal/testing.rb +9 -2
  51. data/lib/action_controller/metal/url_for.rb +1 -1
  52. data/lib/action_controller/metal.rb +16 -17
  53. data/lib/action_controller/railtie.rb +49 -6
  54. data/lib/action_controller/railties/helpers.rb +1 -1
  55. data/lib/action_controller/renderer.rb +37 -13
  56. data/lib/action_controller/template_assertions.rb +1 -1
  57. data/lib/action_controller/test_case.rb +98 -68
  58. data/lib/action_controller.rb +4 -5
  59. data/lib/action_dispatch/http/cache.rb +45 -32
  60. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  61. data/lib/action_dispatch/http/content_security_policy.rb +69 -56
  62. data/lib/action_dispatch/http/filter_parameters.rb +14 -8
  63. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  64. data/lib/action_dispatch/http/headers.rb +4 -4
  65. data/lib/action_dispatch/http/mime_negotiation.rb +44 -16
  66. data/lib/action_dispatch/http/mime_type.rb +47 -30
  67. data/lib/action_dispatch/http/parameters.rb +18 -27
  68. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  69. data/lib/action_dispatch/http/request.rb +49 -35
  70. data/lib/action_dispatch/http/response.rb +34 -26
  71. data/lib/action_dispatch/http/upload.rb +9 -1
  72. data/lib/action_dispatch/http/url.rb +86 -94
  73. data/lib/action_dispatch/journey/formatter.rb +55 -31
  74. data/lib/action_dispatch/journey/gtg/builder.rb +30 -46
  75. data/lib/action_dispatch/journey/gtg/simulator.rb +15 -8
  76. data/lib/action_dispatch/journey/gtg/transition_table.rb +78 -21
  77. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  78. data/lib/action_dispatch/journey/nodes/node.rb +83 -16
  79. data/lib/action_dispatch/journey/parser.rb +13 -13
  80. data/lib/action_dispatch/journey/parser.y +1 -1
  81. data/lib/action_dispatch/journey/path/pattern.rb +42 -34
  82. data/lib/action_dispatch/journey/route.rb +14 -31
  83. data/lib/action_dispatch/journey/router/utils.rb +16 -14
  84. data/lib/action_dispatch/journey/router.rb +27 -35
  85. data/lib/action_dispatch/journey/routes.rb +3 -5
  86. data/lib/action_dispatch/journey/scanner.rb +10 -4
  87. data/lib/action_dispatch/journey/visitors.rb +1 -4
  88. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  89. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  90. data/lib/action_dispatch/journey.rb +0 -2
  91. data/lib/action_dispatch/middleware/actionable_exceptions.rb +45 -0
  92. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  93. data/lib/action_dispatch/middleware/cookies.rb +136 -113
  94. data/lib/action_dispatch/middleware/debug_exceptions.rb +47 -68
  95. data/lib/action_dispatch/middleware/debug_locks.rb +8 -8
  96. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  97. data/lib/action_dispatch/middleware/exception_wrapper.rb +79 -30
  98. data/lib/action_dispatch/middleware/executor.rb +4 -1
  99. data/lib/action_dispatch/middleware/flash.rb +10 -12
  100. data/lib/action_dispatch/middleware/host_authorization.rb +159 -0
  101. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  102. data/lib/action_dispatch/middleware/remote_ip.rb +30 -20
  103. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  104. data/lib/action_dispatch/middleware/server_timing.rb +33 -0
  105. data/lib/action_dispatch/middleware/session/abstract_store.rb +16 -3
  106. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  107. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  108. data/lib/action_dispatch/middleware/show_exceptions.rb +20 -11
  109. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  110. data/lib/action_dispatch/middleware/stack.rb +79 -7
  111. data/lib/action_dispatch/middleware/static.rb +150 -94
  112. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  113. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  114. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +6 -11
  116. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  118. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  119. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +8 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +7 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +25 -6
  122. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +9 -6
  124. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  125. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +121 -15
  126. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +5 -5
  129. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +4 -4
  130. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +5 -5
  131. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  132. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +16 -2
  133. data/lib/action_dispatch/railtie.rb +16 -4
  134. data/lib/action_dispatch/request/session.rb +59 -22
  135. data/lib/action_dispatch/request/utils.rb +28 -2
  136. data/lib/action_dispatch/routing/inspector.rb +102 -54
  137. data/lib/action_dispatch/routing/mapper.rb +184 -156
  138. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  139. data/lib/action_dispatch/routing/redirection.rb +4 -6
  140. data/lib/action_dispatch/routing/route_set.rb +83 -73
  141. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  142. data/lib/action_dispatch/routing/url_for.rb +2 -3
  143. data/lib/action_dispatch/routing.rb +23 -22
  144. data/lib/action_dispatch/system_test_case.rb +65 -16
  145. data/lib/action_dispatch/system_testing/browser.rb +43 -16
  146. data/lib/action_dispatch/system_testing/driver.rb +42 -10
  147. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +58 -12
  148. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +3 -10
  149. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  150. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  151. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  152. data/lib/action_dispatch/testing/assertions.rb +3 -6
  153. data/lib/action_dispatch/testing/integration.rb +61 -30
  154. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  155. data/lib/action_dispatch/testing/test_process.rb +8 -6
  156. data/lib/action_dispatch/testing/test_request.rb +3 -3
  157. data/lib/action_dispatch/testing/test_response.rb +4 -32
  158. data/lib/action_dispatch.rb +15 -7
  159. data/lib/action_pack/gem_version.rb +4 -4
  160. data/lib/action_pack.rb +1 -1
  161. metadata +44 -25
  162. data/lib/action_controller/metal/force_ssl.rb +0 -99
  163. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  164. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  165. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  166. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  167. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
data/README.rdoc CHANGED
@@ -2,9 +2,8 @@
2
2
 
3
3
  Action Pack is a framework for handling and responding to web requests. It
4
4
  provides mechanisms for *routing* (mapping request URLs to actions), defining
5
- *controllers* that implement actions, and generating responses by rendering
6
- *views*, which are templates of various formats. In short, Action Pack
7
- provides the view and controller layers in the MVC paradigm.
5
+ *controllers* that implement actions, and generating responses. In short, Action Pack
6
+ provides the controller layer in the MVC paradigm.
8
7
 
9
8
  It consists of several modules:
10
9
 
@@ -23,6 +22,7 @@ by default and Action View rendering is implicitly triggered by Action
23
22
  Controller. However, these modules are designed to function on their own and
24
23
  can be used outside of Rails.
25
24
 
25
+ You can read more about Action Pack in the {Action Controller Overview}[https://guides.rubyonrails.org/action_controller_overview.html] guide.
26
26
 
27
27
  == Download and installation
28
28
 
@@ -32,7 +32,7 @@ The latest version of Action Pack can be installed with RubyGems:
32
32
 
33
33
  Source code can be downloaded as part of the Rails project on GitHub:
34
34
 
35
- * https://github.com/rails/rails/tree/5-2-stable/actionpack
35
+ * https://github.com/rails/rails/tree/main/actionpack
36
36
 
37
37
 
38
38
  == License
@@ -46,7 +46,7 @@ Action Pack is released under the MIT license:
46
46
 
47
47
  API documentation is at:
48
48
 
49
- * http://api.rubyonrails.org
49
+ * https://api.rubyonrails.org
50
50
 
51
51
  Bug reports for the Ruby on Rails project can be filed here:
52
52
 
@@ -54,4 +54,4 @@ Bug reports for the Ruby on Rails project can be filed here:
54
54
 
55
55
  Feature requests should be discussed on the rails-core mailing list here:
56
56
 
57
- * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
57
+ * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbstractController
4
- module AssetPaths #:nodoc:
4
+ module AssetPaths # :nodoc:
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
@@ -9,6 +9,21 @@ require "active_support/core_ext/module/attr_internal"
9
9
  module AbstractController
10
10
  # Raised when a non-existing controller action is triggered.
11
11
  class ActionNotFound < StandardError
12
+ attr_reader :controller, :action # :nodoc:
13
+
14
+ def initialize(message = nil, controller = nil, action = nil) # :nodoc:
15
+ @controller = controller
16
+ @action = action
17
+ super(message)
18
+ end
19
+
20
+ if defined?(DidYouMean::Correctable) && defined?(DidYouMean::SpellChecker)
21
+ include DidYouMean::Correctable # :nodoc:
22
+
23
+ def corrections # :nodoc:
24
+ @corrections ||= DidYouMean::SpellChecker.new(dictionary: controller.class.action_methods).correct(action)
25
+ end
26
+ end
12
27
  end
13
28
 
14
29
  # AbstractController::Base is a low-level API. Nobody should be
@@ -78,7 +93,9 @@ module AbstractController
78
93
  # Except for public instance methods of Base and its ancestors
79
94
  internal_methods +
80
95
  # Be sure to include shadowed public instance methods of this class
81
- public_instance_methods(false)).uniq.map(&:to_s)
96
+ public_instance_methods(false))
97
+
98
+ methods.map!(&:to_s)
82
99
 
83
100
  methods.to_set
84
101
  end
@@ -102,7 +119,7 @@ module AbstractController
102
119
  # ==== Returns
103
120
  # * <tt>String</tt>
104
121
  def controller_path
105
- @controller_path ||= name.sub(/Controller$/, "".freeze).underscore unless anonymous?
122
+ @controller_path ||= name.delete_suffix("Controller").underscore unless anonymous?
106
123
  end
107
124
 
108
125
  # Refresh the cached action_methods when a new action_method is added.
@@ -126,7 +143,7 @@ module AbstractController
126
143
  @_action_name = action.to_s
127
144
 
128
145
  unless action_name = _find_action_name(@_action_name)
129
- raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
146
+ raise ActionNotFound.new("The action '#{action}' could not be found for #{self.class.name}", self, action)
130
147
  end
131
148
 
132
149
  @_response_body = nil
@@ -173,8 +190,11 @@ module AbstractController
173
190
  true
174
191
  end
175
192
 
176
- private
193
+ def inspect # :nodoc:
194
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
195
+ end
177
196
 
197
+ private
178
198
  # Returns true if the name can be considered an action because
179
199
  # it has a method defined in the controller.
180
200
  #
@@ -28,7 +28,6 @@ module AbstractController
28
28
  self.fragment_cache_keys = []
29
29
 
30
30
  if respond_to?(:helper_method)
31
- helper_method :fragment_cache_key
32
31
  helper_method :combined_fragment_cache_key
33
32
  end
34
33
  end
@@ -60,35 +59,20 @@ module AbstractController
60
59
  end
61
60
  end
62
61
 
63
- # Given a key (as described in +expire_fragment+), returns
64
- # a key suitable for use in reading, writing, or expiring a
65
- # cached fragment. All keys begin with <tt>views/</tt>,
66
- # followed by any controller-wide key prefix values, ending
67
- # with the specified +key+ value. The key is expanded using
68
- # ActiveSupport::Cache.expand_cache_key.
69
- def fragment_cache_key(key)
70
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
71
- Calling fragment_cache_key directly is deprecated and will be removed in Rails 6.0.
72
- All fragment accessors now use the combined_fragment_cache_key method that retains the key as an array,
73
- such that the caching stores can interrogate the parts for cache versions used in
74
- recyclable cache keys.
75
- MSG
76
-
77
- head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
78
- tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
79
- ActiveSupport::Cache.expand_cache_key([*head, *tail], :views)
80
- end
81
-
82
62
  # Given a key (as described in +expire_fragment+), returns
83
63
  # a key array suitable for use in reading, writing, or expiring a
84
64
  # cached fragment. All keys begin with <tt>:views</tt>,
85
- # followed by ENV["RAILS_CACHE_ID"] or ENV["RAILS_APP_VERSION"] if set,
65
+ # followed by <tt>ENV["RAILS_CACHE_ID"]</tt> or <tt>ENV["RAILS_APP_VERSION"]</tt> if set,
86
66
  # followed by any controller-wide key prefix values, ending
87
67
  # with the specified +key+ value.
88
68
  def combined_fragment_cache_key(key)
89
69
  head = self.class.fragment_cache_keys.map { |k| instance_exec(&k) }
90
70
  tail = key.is_a?(Hash) ? url_for(key).split("://").last : key
91
- [ :views, (ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]), *head, *tail ].compact
71
+
72
+ cache_key = [:views, ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"], head, tail]
73
+ cache_key.flatten!(1)
74
+ cache_key.compact!
75
+ cache_key
92
76
  end
93
77
 
94
78
  # Writes +content+ to the location signified by
@@ -158,8 +142,8 @@ module AbstractController
158
142
  end
159
143
  end
160
144
 
161
- def instrument_fragment_cache(name, key) # :nodoc:
162
- ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", instrument_payload(key)) { yield }
145
+ def instrument_fragment_cache(name, key, &block) # :nodoc:
146
+ ActiveSupport::Notifications.instrument("#{name}.#{instrument_name}", instrument_payload(key), &block)
163
147
  end
164
148
  end
165
149
  end
@@ -15,7 +15,7 @@ module AbstractController
15
15
  end
16
16
 
17
17
  def cache_store=(store)
18
- config.cache_store = ActiveSupport::Cache.lookup_store(store)
18
+ config.cache_store = ActiveSupport::Cache.lookup_store(*store)
19
19
  end
20
20
 
21
21
  private
@@ -50,7 +50,7 @@ module AbstractController
50
50
  end
51
51
 
52
52
  def view_cache_dependencies
53
- self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
53
+ self.class._view_cache_dependencies.filter_map { |dep| instance_exec(&dep) }
54
54
  end
55
55
 
56
56
  private
@@ -35,12 +35,18 @@ module AbstractController
35
35
  skip_after_callbacks_if_terminated: true
36
36
  end
37
37
 
38
- # Override <tt>AbstractController::Base#process_action</tt> to run the
39
- # <tt>process_action</tt> callbacks around the normal behavior.
40
- def process_action(*args)
41
- run_callbacks(:process_action) do
42
- super
38
+ class ActionFilter # :nodoc:
39
+ def initialize(actions)
40
+ @actions = Array(actions).map(&:to_s).to_set
43
41
  end
42
+
43
+ def match?(controller)
44
+ @actions.include?(controller.action_name)
45
+ end
46
+
47
+ alias after match?
48
+ alias before match?
49
+ alias around match?
44
50
  end
45
51
 
46
52
  module ClassMethods
@@ -69,9 +75,8 @@ module AbstractController
69
75
  end
70
76
 
71
77
  def _normalize_callback_option(options, from, to) # :nodoc:
72
- if from = options[from]
73
- _from = Array(from).map(&:to_s).to_set
74
- from = proc { |c| _from.include? c.action_name }
78
+ if from = options.delete(from)
79
+ from = ActionFilter.new(from)
75
80
  options[to] = Array(options[to]).unshift(from)
76
81
  end
77
82
  end
@@ -103,6 +108,10 @@ module AbstractController
103
108
  # :call-seq: before_action(names, block)
104
109
  #
105
110
  # Append a callback before actions. See _insert_callbacks for parameter details.
111
+ #
112
+ # If the callback renders or redirects, the action will not run. If there
113
+ # are additional callbacks scheduled to run after that callback, they are
114
+ # also cancelled.
106
115
 
107
116
  ##
108
117
  # :method: prepend_before_action
@@ -110,6 +119,10 @@ module AbstractController
110
119
  # :call-seq: prepend_before_action(names, block)
111
120
  #
112
121
  # Prepend a callback before actions. See _insert_callbacks for parameter details.
122
+ #
123
+ # If the callback renders or redirects, the action will not run. If there
124
+ # are additional callbacks scheduled to run after that callback, they are
125
+ # also cancelled.
113
126
 
114
127
  ##
115
128
  # :method: skip_before_action
@@ -124,6 +137,10 @@ module AbstractController
124
137
  # :call-seq: append_before_action(names, block)
125
138
  #
126
139
  # Append a callback before actions. See _insert_callbacks for parameter details.
140
+ #
141
+ # If the callback renders or redirects, the action will not run. If there
142
+ # are additional callbacks scheduled to run after that callback, they are
143
+ # also cancelled.
127
144
 
128
145
  ##
129
146
  # :method: after_action
@@ -208,5 +225,14 @@ module AbstractController
208
225
  alias_method :"append_#{callback}_action", :"#{callback}_action"
209
226
  end
210
227
  end
228
+
229
+ private
230
+ # Override <tt>AbstractController::Base#process_action</tt> to run the
231
+ # <tt>process_action</tt> callbacks around the normal behavior.
232
+ def process_action(*)
233
+ run_callbacks(:process_action) do
234
+ super
235
+ end
236
+ end
211
237
  end
212
238
  end
@@ -10,6 +10,7 @@ module AbstractController
10
10
  def #{sym}(*args, &block)
11
11
  custom(Mime[:#{sym}], *args, &block)
12
12
  end
13
+ ruby2_keywords(:#{sym})
13
14
  RUBY
14
15
  end
15
16
 
@@ -22,11 +23,10 @@ module AbstractController
22
23
  end
23
24
 
24
25
  private
25
-
26
- def method_missing(symbol, &block)
26
+ def method_missing(symbol, *args, &block)
27
27
  unless mime_constant = Mime[symbol]
28
28
  raise NoMethodError, "To respond to a custom format, register it as a MIME type first: " \
29
- "http://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
29
+ "https://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
30
30
  "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
31
31
  "be sure to nest your variant response within a format response: " \
32
32
  "format.html { |html| html.tablet { ... } }"
@@ -34,10 +34,11 @@ module AbstractController
34
34
 
35
35
  if Mime::SET.include?(mime_constant)
36
36
  AbstractController::Collector.generate_method_for_mime(mime_constant)
37
- send(symbol, &block)
37
+ public_send(symbol, *args, &block)
38
38
  else
39
39
  super
40
40
  end
41
41
  end
42
+ ruby2_keywords(:method_missing)
42
43
  end
43
44
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbstractController
4
- class Error < StandardError #:nodoc:
4
+ class Error < StandardError # :nodoc:
5
5
  end
6
6
  end
@@ -1,14 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/dependencies"
4
+ require "active_support/core_ext/name_error"
4
5
 
5
6
  module AbstractController
6
7
  module Helpers
7
8
  extend ActiveSupport::Concern
8
9
 
9
10
  included do
10
- class_attribute :_helpers, default: Module.new
11
11
  class_attribute :_helper_methods, default: Array.new
12
+
13
+ # This is here so that it is always higher in the inheritance chain than
14
+ # the definition in lib/action_view/rendering.rb
15
+ redefine_singleton_method(:_helpers) do
16
+ if @_helpers ||= nil
17
+ @_helpers
18
+ else
19
+ superclass._helpers
20
+ end
21
+ end
22
+
23
+ self._helpers = define_helpers_module(self)
12
24
  end
13
25
 
14
26
  class MissingHelperError < LoadError
@@ -17,7 +29,7 @@ module AbstractController
17
29
  @path = "helpers/#{path}.rb"
18
30
  set_backtrace error.backtrace
19
31
 
20
- if error.path =~ /^#{path}(\.rb)?$/
32
+ if /^#{path}(\.rb)?$/.match?(error.path)
21
33
  super("Missing helper file helpers/%s.rb" % path)
22
34
  else
23
35
  raise error
@@ -25,17 +37,24 @@ module AbstractController
25
37
  end
26
38
  end
27
39
 
40
+ def _helpers
41
+ self.class._helpers
42
+ end
43
+
28
44
  module ClassMethods
29
45
  # When a class is inherited, wrap its helper module in a new module.
30
46
  # This ensures that the parent class's module can be changed
31
47
  # independently of the child class's.
32
48
  def inherited(klass)
33
- helpers = _helpers
34
- klass._helpers = Module.new { include helpers }
49
+ # Inherited from parent by default
50
+ klass._helpers = nil
51
+
35
52
  klass.class_eval { default_helper_module! } unless klass.anonymous?
36
53
  super
37
54
  end
38
55
 
56
+ attr_writer :_helpers
57
+
39
58
  # Declare a controller method as a helper. For example, the following
40
59
  # makes the +current_user+ and +logged_in?+ controller methods available
41
60
  # to the view:
@@ -57,59 +76,81 @@ module AbstractController
57
76
  # ==== Parameters
58
77
  # * <tt>method[, method]</tt> - A name or names of a method on the controller
59
78
  # to be made available on the view.
60
- def helper_method(*meths)
61
- meths.flatten!
62
- self._helper_methods += meths
63
-
64
- meths.each do |meth|
65
- _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
66
- def #{meth}(*args, &blk) # def current_user(*args, &blk)
67
- controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk)
68
- end # end
79
+ def helper_method(*methods)
80
+ methods.flatten!
81
+ self._helper_methods += methods
82
+
83
+ location = caller_locations(1, 1).first
84
+ file, line = location.path, location.lineno
85
+
86
+ methods.each do |method|
87
+ _helpers_for_modification.class_eval <<~ruby_eval, file, line
88
+ def #{method}(*args, &block) # def current_user(*args, &block)
89
+ controller.send(:'#{method}', *args, &block) # controller.send(:'current_user', *args, &block)
90
+ end # end
91
+ ruby2_keywords(:'#{method}')
69
92
  ruby_eval
70
93
  end
71
94
  end
72
95
 
73
- # The +helper+ class method can take a series of helper module names, a block, or both.
96
+ # Includes the given modules in the template class.
97
+ #
98
+ # Modules can be specified in different ways. All of the following calls
99
+ # include +FooHelper+:
100
+ #
101
+ # # Module, recommended.
102
+ # helper FooHelper
103
+ #
104
+ # # String/symbol without the "helper" suffix, camel or snake case.
105
+ # helper "Foo"
106
+ # helper :Foo
107
+ # helper "foo"
108
+ # helper :foo
109
+ #
110
+ # The last two assume that <tt>"foo".camelize</tt> returns "Foo".
74
111
  #
75
- # ==== Options
76
- # * <tt>*args</tt> - Module, Symbol, String
77
- # * <tt>block</tt> - A block defining helper methods
112
+ # When strings or symbols are passed, the method finds the actual module
113
+ # object using +String#constantize+. Therefore, if the module has not been
114
+ # yet loaded, it has to be autoloadable, which is normally the case.
78
115
  #
79
- # When the argument is a module it will be included directly in the template class.
80
- # helper FooHelper # => includes FooHelper
116
+ # Namespaces are supported. The following calls include +Foo::BarHelper+:
81
117
  #
82
- # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
83
- # and include the module in the template class. The second form illustrates how to include custom helpers
84
- # when working with namespaced controllers, or other cases where the file containing the helper definition is not
85
- # in one of Rails' standard load paths:
86
- # helper :foo # => requires 'foo_helper' and includes FooHelper
87
- # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
118
+ # # Module, recommended.
119
+ # helper Foo::BarHelper
88
120
  #
89
- # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
90
- # to the template.
121
+ # # String/symbol without the "helper" suffix, camel or snake case.
122
+ # helper "Foo::Bar"
123
+ # helper :"Foo::Bar"
124
+ # helper "foo/bar"
125
+ # helper :"foo/bar"
91
126
  #
92
- # # One line
93
- # helper { def hello() "Hello, world!" end }
127
+ # The last two assume that <tt>"foo/bar".camelize</tt> returns "Foo::Bar".
128
+ #
129
+ # The method accepts a block too. If present, the block is evaluated in
130
+ # the context of the controller helper module. This simple call makes the
131
+ # +wadus+ method available in templates of the enclosing controller:
94
132
  #
95
- # # Multi-line
96
133
  # helper do
97
- # def foo(bar)
98
- # "#{bar} is the very best"
134
+ # def wadus
135
+ # "wadus"
99
136
  # end
100
137
  # end
101
138
  #
102
- # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
103
- # +symbols+, +strings+, +modules+ and blocks.
139
+ # Furthermore, all the above styles can be mixed together:
104
140
  #
105
- # helper(:three, BlindHelper) { def mice() 'mice' end }
141
+ # helper FooHelper, "woo", "bar/baz" do
142
+ # def wadus
143
+ # "wadus"
144
+ # end
145
+ # end
106
146
  #
107
147
  def helper(*args, &block)
108
148
  modules_for_helpers(args).each do |mod|
109
- add_template_helper(mod)
149
+ next if _helpers.include?(mod)
150
+ _helpers_for_modification.include(mod)
110
151
  end
111
152
 
112
- _helpers.module_eval(&block) if block_given?
153
+ _helpers_for_modification.module_eval(&block) if block_given?
113
154
  end
114
155
 
115
156
  # Clears up all existing helpers in this class, only keeping the helper
@@ -123,71 +164,47 @@ module AbstractController
123
164
  default_helper_module! unless anonymous?
124
165
  end
125
166
 
126
- # Returns a list of modules, normalized from the acceptable kinds of
127
- # helpers with the following behavior:
128
- #
129
- # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
130
- # and "foo_bar_helper.rb" is loaded using require_dependency.
131
- #
132
- # Module:: No further processing
133
- #
134
- # After loading the appropriate files, the corresponding modules
135
- # are returned.
136
- #
137
- # ==== Parameters
138
- # * <tt>args</tt> - An array of helpers
139
- #
140
- # ==== Returns
141
- # * <tt>Array</tt> - A normalized list of modules for the list of
142
- # helpers provided.
143
- def modules_for_helpers(args)
144
- args.flatten.map! do |arg|
145
- case arg
146
- when String, Symbol
147
- file_name = "#{arg.to_s.underscore}_helper"
148
- begin
149
- require_dependency(file_name)
150
- rescue LoadError => e
151
- raise AbstractController::Helpers::MissingHelperError.new(e, file_name)
152
- end
153
-
154
- mod_name = file_name.camelize
155
- begin
156
- mod_name.constantize
157
- rescue LoadError
158
- # dependencies.rb gives a similar error message but its wording is
159
- # not as clear because it mentions autoloading. To the user all it
160
- # matters is that a helper module couldn't be loaded, autoloading
161
- # is an internal mechanism that should not leak.
162
- raise NameError, "Couldn't find #{mod_name}, expected it to be defined in helpers/#{file_name}.rb"
163
- end
167
+ # Given an array of values like the ones accepted by +helper+, this method
168
+ # returns an array with the corresponding modules, in the same order.
169
+ def modules_for_helpers(modules_or_helper_prefixes)
170
+ modules_or_helper_prefixes.flatten.map! do |module_or_helper_prefix|
171
+ case module_or_helper_prefix
164
172
  when Module
165
- arg
173
+ module_or_helper_prefix
174
+ when String, Symbol
175
+ helper_prefix = module_or_helper_prefix.to_s
176
+ helper_prefix = helper_prefix.camelize unless helper_prefix.start_with?(/[A-Z]/)
177
+ "#{helper_prefix}Helper".constantize
166
178
  else
167
179
  raise ArgumentError, "helper must be a String, Symbol, or Module"
168
180
  end
169
181
  end
170
182
  end
171
183
 
184
+ def _helpers_for_modification
185
+ unless @_helpers
186
+ self._helpers = define_helpers_module(self, superclass._helpers)
187
+ end
188
+ _helpers
189
+ end
190
+
172
191
  private
173
- # Makes all the (instance) methods in the helper module available to templates
174
- # rendered through this controller.
175
- #
176
- # ==== Parameters
177
- # * <tt>module</tt> - The module to include into the current helper module
178
- # for the class
179
- def add_template_helper(mod)
180
- _helpers.module_eval { include mod }
192
+ def define_helpers_module(klass, helpers = nil)
193
+ # In some tests inherited is called explicitly. In that case, just
194
+ # return the module from the first time it was defined
195
+ return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false)
196
+
197
+ mod = Module.new
198
+ klass.const_set(:HelperMethods, mod)
199
+ mod.include(helpers) if helpers
200
+ mod
181
201
  end
182
202
 
183
203
  def default_helper_module!
184
- module_name = name.sub(/Controller$/, "".freeze)
185
- module_path = module_name.underscore
186
- helper module_path
187
- rescue LoadError => e
188
- raise e unless e.is_missing? "helpers/#{module_path}_helper"
204
+ helper_prefix = name.delete_suffix("Controller")
205
+ helper(helper_prefix)
189
206
  rescue NameError => e
190
- raise e unless e.missing_name? "#{module_name}Helper"
207
+ raise unless e.missing_name?("#{helper_prefix}Helper")
191
208
  end
192
209
  end
193
210
  end
@@ -3,7 +3,7 @@
3
3
  require "active_support/benchmarkable"
4
4
 
5
5
  module AbstractController
6
- module Logger #:nodoc:
6
+ module Logger # :nodoc:
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/module/introspection"
4
+
3
5
  module AbstractController
4
6
  module Railties
5
7
  module RoutesHelpers
@@ -7,11 +9,27 @@ module AbstractController
7
9
  Module.new do
8
10
  define_method(:inherited) do |klass|
9
11
  super(klass)
10
- if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
12
+
13
+ namespace = klass.module_parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) }
14
+ actual_routes = namespace ? namespace.railtie_routes_url_helpers._routes : routes
15
+
16
+ if namespace
11
17
  klass.include(namespace.railtie_routes_url_helpers(include_path_helpers))
12
18
  else
13
19
  klass.include(routes.url_helpers(include_path_helpers))
14
20
  end
21
+
22
+ # In the case that we have ex.
23
+ # class A::Foo < ApplicationController
24
+ # class Bar < A::Foo
25
+ # We will need to redefine _routes because it will not be correct
26
+ # via inheritance.
27
+ unless klass._routes.equal?(actual_routes)
28
+ klass.redefine_singleton_method(:_routes) { actual_routes }
29
+ klass.include(Module.new do
30
+ define_method(:_routes) { @_routes || actual_routes }
31
+ end)
32
+ end
15
33
  end
16
34
  end
17
35
  end