actionpack 5.2.4.4 → 6.1.1

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 (155) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +264 -322
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller.rb +1 -0
  6. data/lib/abstract_controller/base.rb +38 -4
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/caching/fragments.rb +6 -22
  9. data/lib/abstract_controller/callbacks.rb +14 -2
  10. data/lib/abstract_controller/collector.rb +1 -2
  11. data/lib/abstract_controller/helpers.rb +106 -90
  12. data/lib/abstract_controller/railties/routes_helpers.rb +1 -1
  13. data/lib/abstract_controller/rendering.rb +9 -9
  14. data/lib/abstract_controller/translation.rb +11 -5
  15. data/lib/action_controller.rb +7 -4
  16. data/lib/action_controller/api.rb +4 -3
  17. data/lib/action_controller/base.rb +6 -9
  18. data/lib/action_controller/caching.rb +1 -3
  19. data/lib/action_controller/log_subscriber.rb +10 -7
  20. data/lib/action_controller/metal.rb +10 -8
  21. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  22. data/lib/action_controller/metal/conditional_get.rb +19 -5
  23. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  24. data/lib/action_controller/metal/cookies.rb +3 -1
  25. data/lib/action_controller/metal/data_streaming.rb +6 -7
  26. data/lib/action_controller/metal/default_headers.rb +17 -0
  27. data/lib/action_controller/metal/etag_with_template_digest.rb +3 -5
  28. data/lib/action_controller/metal/exceptions.rb +56 -2
  29. data/lib/action_controller/metal/flash.rb +5 -5
  30. data/lib/action_controller/metal/head.rb +7 -4
  31. data/lib/action_controller/metal/helpers.rb +14 -5
  32. data/lib/action_controller/metal/http_authentication.rb +24 -23
  33. data/lib/action_controller/metal/implicit_render.rb +5 -15
  34. data/lib/action_controller/metal/instrumentation.rb +13 -14
  35. data/lib/action_controller/metal/live.rb +30 -32
  36. data/lib/action_controller/metal/logging.rb +20 -0
  37. data/lib/action_controller/metal/mime_responds.rb +19 -4
  38. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  39. data/lib/action_controller/metal/params_wrapper.rb +31 -22
  40. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  41. data/lib/action_controller/metal/redirecting.rb +6 -6
  42. data/lib/action_controller/metal/renderers.rb +4 -4
  43. data/lib/action_controller/metal/rendering.rb +8 -3
  44. data/lib/action_controller/metal/request_forgery_protection.rb +62 -34
  45. data/lib/action_controller/metal/rescue.rb +1 -1
  46. data/lib/action_controller/metal/streaming.rb +0 -1
  47. data/lib/action_controller/metal/strong_parameters.rb +167 -58
  48. data/lib/action_controller/metal/url_for.rb +1 -1
  49. data/lib/action_controller/railties/helpers.rb +1 -1
  50. data/lib/action_controller/renderer.rb +37 -13
  51. data/lib/action_controller/template_assertions.rb +1 -1
  52. data/lib/action_controller/test_case.rb +70 -65
  53. data/lib/action_dispatch.rb +9 -3
  54. data/lib/action_dispatch/http/cache.rb +26 -21
  55. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  56. data/lib/action_dispatch/http/content_security_policy.rb +33 -19
  57. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  58. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  59. data/lib/action_dispatch/http/headers.rb +4 -4
  60. data/lib/action_dispatch/http/mime_negotiation.rb +26 -13
  61. data/lib/action_dispatch/http/mime_type.rb +42 -23
  62. data/lib/action_dispatch/http/parameters.rb +14 -23
  63. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  64. data/lib/action_dispatch/http/request.rb +45 -22
  65. data/lib/action_dispatch/http/response.rb +45 -25
  66. data/lib/action_dispatch/http/upload.rb +9 -1
  67. data/lib/action_dispatch/http/url.rb +82 -82
  68. data/lib/action_dispatch/journey.rb +0 -2
  69. data/lib/action_dispatch/journey/formatter.rb +54 -30
  70. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  71. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  72. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  73. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  74. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  75. data/lib/action_dispatch/journey/parser.rb +13 -13
  76. data/lib/action_dispatch/journey/parser.y +1 -1
  77. data/lib/action_dispatch/journey/path/pattern.rb +19 -21
  78. data/lib/action_dispatch/journey/route.rb +10 -20
  79. data/lib/action_dispatch/journey/router.rb +26 -34
  80. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  81. data/lib/action_dispatch/journey/routes.rb +0 -2
  82. data/lib/action_dispatch/journey/scanner.rb +10 -4
  83. data/lib/action_dispatch/journey/visitors.rb +1 -4
  84. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  85. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  86. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  87. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  88. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  89. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  90. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  91. data/lib/action_dispatch/middleware/flash.rb +1 -1
  92. data/lib/action_dispatch/middleware/host_authorization.rb +121 -0
  93. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  94. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  95. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  96. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -3
  97. data/lib/action_dispatch/middleware/session/cookie_store.rb +3 -9
  98. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  99. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  100. data/lib/action_dispatch/middleware/stack.rb +56 -2
  101. data/lib/action_dispatch/middleware/static.rb +153 -93
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  107. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  108. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  114. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -1
  115. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  123. data/lib/action_dispatch/railtie.rb +8 -2
  124. data/lib/action_dispatch/request/session.rb +10 -9
  125. data/lib/action_dispatch/request/utils.rb +26 -2
  126. data/lib/action_dispatch/routing.rb +21 -20
  127. data/lib/action_dispatch/routing/inspector.rb +100 -52
  128. data/lib/action_dispatch/routing/mapper.rb +155 -103
  129. data/lib/action_dispatch/routing/polymorphic_routes.rb +13 -15
  130. data/lib/action_dispatch/routing/redirection.rb +3 -3
  131. data/lib/action_dispatch/routing/route_set.rb +71 -69
  132. data/lib/action_dispatch/routing/url_for.rb +2 -2
  133. data/lib/action_dispatch/system_test_case.rb +54 -11
  134. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  135. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  136. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  137. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -10
  138. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  139. data/lib/action_dispatch/testing/assertions.rb +1 -1
  140. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  141. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  142. data/lib/action_dispatch/testing/integration.rb +61 -28
  143. data/lib/action_dispatch/testing/request_encoder.rb +2 -2
  144. data/lib/action_dispatch/testing/test_process.rb +29 -4
  145. data/lib/action_dispatch/testing/test_request.rb +3 -3
  146. data/lib/action_dispatch/testing/test_response.rb +4 -32
  147. data/lib/action_pack.rb +1 -1
  148. data/lib/action_pack/gem_version.rb +4 -4
  149. metadata +38 -26
  150. data/lib/action_controller/metal/force_ssl.rb +0 -99
  151. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  152. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  153. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  154. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  155. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2004-2018 David Heinemeier Hansson
1
+ Copyright (c) 2004-2020 David Heinemeier Hansson
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -23,6 +23,7 @@ by default and Action View rendering is implicitly triggered by Action
23
23
  Controller. However, these modules are designed to function on their own and
24
24
  can be used outside of Rails.
25
25
 
26
+ You can read more about Action Pack in the {Action Controller Overview}[https://guides.rubyonrails.org/action_controller_overview.html] guide.
26
27
 
27
28
  == Download and installation
28
29
 
@@ -32,7 +33,7 @@ The latest version of Action Pack can be installed with RubyGems:
32
33
 
33
34
  Source code can be downloaded as part of the Rails project on GitHub:
34
35
 
35
- * https://github.com/rails/rails/tree/5-2-stable/actionpack
36
+ * https://github.com/rails/rails/tree/master/actionpack
36
37
 
37
38
 
38
39
  == License
@@ -46,7 +47,7 @@ Action Pack is released under the MIT license:
46
47
 
47
48
  API documentation is at:
48
49
 
49
- * http://api.rubyonrails.org
50
+ * https://api.rubyonrails.org
50
51
 
51
52
  Bug reports for the Ruby on Rails project can be filed here:
52
53
 
@@ -54,4 +55,4 @@ Bug reports for the Ruby on Rails project can be filed here:
54
55
 
55
56
  Feature requests should be discussed on the rails-core mailing list here:
56
57
 
57
- * https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
58
+ * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "action_pack"
4
+ require "active_support"
4
5
  require "active_support/rails"
5
6
  require "active_support/i18n"
6
7
 
@@ -9,6 +9,35 @@ 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
13
+ def initialize(message = nil, controller = nil, action = nil)
14
+ @controller = controller
15
+ @action = action
16
+ super(message)
17
+ end
18
+
19
+ class Correction
20
+ def initialize(error)
21
+ @error = error
22
+ end
23
+
24
+ def corrections
25
+ if @error.action
26
+ maybe_these = @error.controller.class.action_methods
27
+
28
+ maybe_these.sort_by { |n|
29
+ DidYouMean::Jaro.distance(@error.action.to_s, n)
30
+ }.reverse.first(4)
31
+ else
32
+ []
33
+ end
34
+ end
35
+ end
36
+
37
+ # We may not have DYM, and DYM might not let us register error handlers
38
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
39
+ DidYouMean.correct_error(self, Correction)
40
+ end
12
41
  end
13
42
 
14
43
  # AbstractController::Base is a low-level API. Nobody should be
@@ -78,7 +107,9 @@ module AbstractController
78
107
  # Except for public instance methods of Base and its ancestors
79
108
  internal_methods +
80
109
  # Be sure to include shadowed public instance methods of this class
81
- public_instance_methods(false)).uniq.map(&:to_s)
110
+ public_instance_methods(false))
111
+
112
+ methods.map!(&:to_s)
82
113
 
83
114
  methods.to_set
84
115
  end
@@ -102,7 +133,7 @@ module AbstractController
102
133
  # ==== Returns
103
134
  # * <tt>String</tt>
104
135
  def controller_path
105
- @controller_path ||= name.sub(/Controller$/, "".freeze).underscore unless anonymous?
136
+ @controller_path ||= name.delete_suffix("Controller").underscore unless anonymous?
106
137
  end
107
138
 
108
139
  # Refresh the cached action_methods when a new action_method is added.
@@ -126,7 +157,7 @@ module AbstractController
126
157
  @_action_name = action.to_s
127
158
 
128
159
  unless action_name = _find_action_name(@_action_name)
129
- raise ActionNotFound, "The action '#{action}' could not be found for #{self.class.name}"
160
+ raise ActionNotFound.new("The action '#{action}' could not be found for #{self.class.name}", self, action)
130
161
  end
131
162
 
132
163
  @_response_body = nil
@@ -173,8 +204,11 @@ module AbstractController
173
204
  true
174
205
  end
175
206
 
176
- private
207
+ def inspect # :nodoc:
208
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
209
+ end
177
210
 
211
+ private
178
212
  # Returns true if the name can be considered an action because
179
213
  # it has a method defined in the controller.
180
214
  #
@@ -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
@@ -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
@@ -37,7 +37,7 @@ module AbstractController
37
37
 
38
38
  # Override <tt>AbstractController::Base#process_action</tt> to run the
39
39
  # <tt>process_action</tt> callbacks around the normal behavior.
40
- def process_action(*args)
40
+ def process_action(*)
41
41
  run_callbacks(:process_action) do
42
42
  super
43
43
  end
@@ -69,7 +69,7 @@ module AbstractController
69
69
  end
70
70
 
71
71
  def _normalize_callback_option(options, from, to) # :nodoc:
72
- if from = options[from]
72
+ if from = options.delete(from)
73
73
  _from = Array(from).map(&:to_s).to_set
74
74
  from = proc { |c| _from.include? c.action_name }
75
75
  options[to] = Array(options[to]).unshift(from)
@@ -103,6 +103,10 @@ module AbstractController
103
103
  # :call-seq: before_action(names, block)
104
104
  #
105
105
  # Append a callback before actions. See _insert_callbacks for parameter details.
106
+ #
107
+ # If the callback renders or redirects, the action will not run. If there
108
+ # are additional callbacks scheduled to run after that callback, they are
109
+ # also cancelled.
106
110
 
107
111
  ##
108
112
  # :method: prepend_before_action
@@ -110,6 +114,10 @@ module AbstractController
110
114
  # :call-seq: prepend_before_action(names, block)
111
115
  #
112
116
  # Prepend a callback before actions. See _insert_callbacks for parameter details.
117
+ #
118
+ # If the callback renders or redirects, the action will not run. If there
119
+ # are additional callbacks scheduled to run after that callback, they are
120
+ # also cancelled.
113
121
 
114
122
  ##
115
123
  # :method: skip_before_action
@@ -124,6 +132,10 @@ module AbstractController
124
132
  # :call-seq: append_before_action(names, block)
125
133
  #
126
134
  # Append a callback before actions. See _insert_callbacks for parameter details.
135
+ #
136
+ # If the callback renders or redirects, the action will not run. If there
137
+ # are additional callbacks scheduled to run after that callback, they are
138
+ # also cancelled.
127
139
 
128
140
  ##
129
141
  # :method: after_action
@@ -22,11 +22,10 @@ module AbstractController
22
22
  end
23
23
 
24
24
  private
25
-
26
25
  def method_missing(symbol, &block)
27
26
  unless mime_constant = Mime[symbol]
28
27
  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. " \
28
+ "https://guides.rubyonrails.org/action_controller_overview.html#restful-downloads. " \
30
29
  "If you meant to respond to a variant like :tablet or :phone, not a custom format, " \
31
30
  "be sure to nest your variant response within a format response: " \
32
31
  "format.html { |html| html.tablet { ... } }"
@@ -7,8 +7,19 @@ module AbstractController
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- class_attribute :_helpers, default: Module.new
11
10
  class_attribute :_helper_methods, default: Array.new
11
+
12
+ # This is here so that it is always higher in the inheritance chain than
13
+ # the definition in lib/action_view/rendering.rb
14
+ redefine_singleton_method(:_helpers) do
15
+ if @_helpers ||= nil
16
+ @_helpers
17
+ else
18
+ superclass._helpers
19
+ end
20
+ end
21
+
22
+ self._helpers = define_helpers_module(self)
12
23
  end
13
24
 
14
25
  class MissingHelperError < LoadError
@@ -17,7 +28,7 @@ module AbstractController
17
28
  @path = "helpers/#{path}.rb"
18
29
  set_backtrace error.backtrace
19
30
 
20
- if error.path =~ /^#{path}(\.rb)?$/
31
+ if /^#{path}(\.rb)?$/.match?(error.path)
21
32
  super("Missing helper file helpers/%s.rb" % path)
22
33
  else
23
34
  raise error
@@ -25,17 +36,24 @@ module AbstractController
25
36
  end
26
37
  end
27
38
 
39
+ def _helpers
40
+ self.class._helpers
41
+ end
42
+
28
43
  module ClassMethods
29
44
  # When a class is inherited, wrap its helper module in a new module.
30
45
  # This ensures that the parent class's module can be changed
31
46
  # independently of the child class's.
32
47
  def inherited(klass)
33
- helpers = _helpers
34
- klass._helpers = Module.new { include helpers }
48
+ # Inherited from parent by default
49
+ klass._helpers = nil
50
+
35
51
  klass.class_eval { default_helper_module! } unless klass.anonymous?
36
52
  super
37
53
  end
38
54
 
55
+ attr_writer :_helpers
56
+
39
57
  # Declare a controller method as a helper. For example, the following
40
58
  # makes the +current_user+ and +logged_in?+ controller methods available
41
59
  # to the view:
@@ -57,59 +75,81 @@ module AbstractController
57
75
  # ==== Parameters
58
76
  # * <tt>method[, method]</tt> - A name or names of a method on the controller
59
77
  # 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
78
+ def helper_method(*methods)
79
+ methods.flatten!
80
+ self._helper_methods += methods
81
+
82
+ location = caller_locations(1, 1).first
83
+ file, line = location.path, location.lineno
84
+
85
+ methods.each do |method|
86
+ _helpers_for_modification.class_eval <<-ruby_eval, file, line
87
+ def #{method}(*args, &block) # def current_user(*args, &block)
88
+ controller.send(:'#{method}', *args, &block) # controller.send(:'current_user', *args, &block)
89
+ end # end
90
+ ruby2_keywords(:'#{method}') if respond_to?(:ruby2_keywords, true)
69
91
  ruby_eval
70
92
  end
71
93
  end
72
94
 
73
- # The +helper+ class method can take a series of helper module names, a block, or both.
95
+ # Includes the given modules in the template class.
96
+ #
97
+ # Modules can be specified in different ways. All of the following calls
98
+ # include +FooHelper+:
99
+ #
100
+ # # Module, recommended.
101
+ # helper FooHelper
102
+ #
103
+ # # String/symbol without the "helper" suffix, camel or snake case.
104
+ # helper "Foo"
105
+ # helper :Foo
106
+ # helper "foo"
107
+ # helper :foo
108
+ #
109
+ # The last two assume that <tt>"foo".camelize</tt> returns "Foo".
74
110
  #
75
- # ==== Options
76
- # * <tt>*args</tt> - Module, Symbol, String
77
- # * <tt>block</tt> - A block defining helper methods
111
+ # When strings or symbols are passed, the method finds the actual module
112
+ # object using +String#constantize+. Therefore, if the module has not been
113
+ # yet loaded, it has to be autoloadable, which is normally the case.
78
114
  #
79
- # When the argument is a module it will be included directly in the template class.
80
- # helper FooHelper # => includes FooHelper
115
+ # Namespaces are supported. The following calls include +Foo::BarHelper+:
81
116
  #
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
117
+ # # Module, recommended.
118
+ # helper Foo::BarHelper
88
119
  #
89
- # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
90
- # to the template.
120
+ # # String/symbol without the "helper" suffix, camel or snake case.
121
+ # helper "Foo::Bar"
122
+ # helper :"Foo::Bar"
123
+ # helper "foo/bar"
124
+ # helper :"foo/bar"
91
125
  #
92
- # # One line
93
- # helper { def hello() "Hello, world!" end }
126
+ # The last two assume that <tt>"foo/bar".camelize</tt> returns "Foo::Bar".
127
+ #
128
+ # The method accepts a block too. If present, the block is evaluated in
129
+ # the context of the controller helper module. This simple call makes the
130
+ # +wadus+ method available in templates of the enclosing controller:
94
131
  #
95
- # # Multi-line
96
132
  # helper do
97
- # def foo(bar)
98
- # "#{bar} is the very best"
133
+ # def wadus
134
+ # "wadus"
99
135
  # end
100
136
  # end
101
137
  #
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.
138
+ # Furthermore, all the above styles can be mixed together:
104
139
  #
105
- # helper(:three, BlindHelper) { def mice() 'mice' end }
140
+ # helper FooHelper, "woo", "bar/baz" do
141
+ # def wadus
142
+ # "wadus"
143
+ # end
144
+ # end
106
145
  #
107
146
  def helper(*args, &block)
108
147
  modules_for_helpers(args).each do |mod|
109
- add_template_helper(mod)
148
+ next if _helpers.include?(mod)
149
+ _helpers_for_modification.include(mod)
110
150
  end
111
151
 
112
- _helpers.module_eval(&block) if block_given?
152
+ _helpers_for_modification.module_eval(&block) if block_given?
113
153
  end
114
154
 
115
155
  # Clears up all existing helpers in this class, only keeping the helper
@@ -123,71 +163,47 @@ module AbstractController
123
163
  default_helper_module! unless anonymous?
124
164
  end
125
165
 
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
166
+ # Given an array of values like the ones accepted by +helper+, this method
167
+ # returns an array with the corresponding modules, in the same order.
168
+ def modules_for_helpers(modules_or_helper_prefixes)
169
+ modules_or_helper_prefixes.flatten.map! do |module_or_helper_prefix|
170
+ case module_or_helper_prefix
164
171
  when Module
165
- arg
172
+ module_or_helper_prefix
173
+ when String, Symbol
174
+ helper_prefix = module_or_helper_prefix.to_s
175
+ helper_prefix = helper_prefix.camelize unless helper_prefix.start_with?(/[A-Z]/)
176
+ "#{helper_prefix}Helper".constantize
166
177
  else
167
178
  raise ArgumentError, "helper must be a String, Symbol, or Module"
168
179
  end
169
180
  end
170
181
  end
171
182
 
183
+ def _helpers_for_modification
184
+ unless @_helpers
185
+ self._helpers = define_helpers_module(self, superclass._helpers)
186
+ end
187
+ _helpers
188
+ end
189
+
172
190
  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 }
191
+ def define_helpers_module(klass, helpers = nil)
192
+ # In some tests inherited is called explicitly. In that case, just
193
+ # return the module from the first time it was defined
194
+ return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false)
195
+
196
+ mod = Module.new
197
+ klass.const_set(:HelperMethods, mod)
198
+ mod.include(helpers) if helpers
199
+ mod
181
200
  end
182
201
 
183
202
  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"
203
+ helper_prefix = name.delete_suffix("Controller")
204
+ helper(helper_prefix)
189
205
  rescue NameError => e
190
- raise e unless e.missing_name? "#{module_name}Helper"
206
+ raise unless e.missing_name?("#{helper_prefix}Helper")
191
207
  end
192
208
  end
193
209
  end