actionpack 4.2.11.1 → 6.1.3.2

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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +291 -489
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +81 -51
  7. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +64 -17
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/callbacks.rb +61 -33
  10. data/lib/abstract_controller/collector.rb +9 -13
  11. data/lib/abstract_controller/error.rb +6 -0
  12. data/lib/abstract_controller/helpers.rb +115 -99
  13. data/lib/abstract_controller/logger.rb +2 -0
  14. data/lib/abstract_controller/railties/routes_helpers.rb +21 -3
  15. data/lib/abstract_controller/rendering.rb +48 -47
  16. data/lib/abstract_controller/translation.rb +17 -8
  17. data/lib/abstract_controller/url_for.rb +2 -0
  18. data/lib/abstract_controller.rb +13 -5
  19. data/lib/action_controller/api/api_rendering.rb +16 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/base.rb +29 -24
  22. data/lib/action_controller/caching.rb +12 -57
  23. data/lib/action_controller/form_builder.rb +50 -0
  24. data/lib/action_controller/log_subscriber.rb +17 -19
  25. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  26. data/lib/action_controller/metal/conditional_get.rb +134 -46
  27. data/lib/action_controller/metal/content_security_policy.rb +51 -0
  28. data/lib/action_controller/metal/cookies.rb +6 -4
  29. data/lib/action_controller/metal/data_streaming.rb +30 -50
  30. data/lib/action_controller/metal/default_headers.rb +17 -0
  31. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  32. data/lib/action_controller/metal/etag_with_template_digest.rb +21 -16
  33. data/lib/action_controller/metal/exceptions.rb +63 -15
  34. data/lib/action_controller/metal/flash.rb +9 -8
  35. data/lib/action_controller/metal/head.rb +26 -21
  36. data/lib/action_controller/metal/helpers.rb +37 -18
  37. data/lib/action_controller/metal/http_authentication.rb +81 -73
  38. data/lib/action_controller/metal/implicit_render.rb +53 -9
  39. data/lib/action_controller/metal/instrumentation.rb +32 -35
  40. data/lib/action_controller/metal/live.rb +102 -120
  41. data/lib/action_controller/metal/logging.rb +20 -0
  42. data/lib/action_controller/metal/mime_responds.rb +49 -47
  43. data/lib/action_controller/metal/parameter_encoding.rb +82 -0
  44. data/lib/action_controller/metal/params_wrapper.rb +83 -66
  45. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  46. data/lib/action_controller/metal/redirecting.rb +53 -32
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +77 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +267 -103
  50. data/lib/action_controller/metal/rescue.rb +10 -17
  51. data/lib/action_controller/metal/streaming.rb +12 -11
  52. data/lib/action_controller/metal/strong_parameters.rb +714 -186
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/metal.rb +104 -87
  56. data/lib/action_controller/railtie.rb +28 -10
  57. data/lib/action_controller/railties/helpers.rb +3 -1
  58. data/lib/action_controller/renderer.rb +141 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +296 -422
  61. data/lib/action_controller.rb +34 -23
  62. data/lib/action_dispatch/http/cache.rb +107 -56
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +286 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +32 -25
  66. data/lib/action_dispatch/http/filter_redirect.rb +10 -12
  67. data/lib/action_dispatch/http/headers.rb +55 -22
  68. data/lib/action_dispatch/http/mime_negotiation.rb +79 -51
  69. data/lib/action_dispatch/http/mime_type.rb +153 -121
  70. data/lib/action_dispatch/http/mime_types.rb +20 -6
  71. data/lib/action_dispatch/http/parameters.rb +90 -40
  72. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  74. data/lib/action_dispatch/http/request.rb +226 -121
  75. data/lib/action_dispatch/http/response.rb +248 -113
  76. data/lib/action_dispatch/http/upload.rb +21 -7
  77. data/lib/action_dispatch/http/url.rb +182 -100
  78. data/lib/action_dispatch/journey/formatter.rb +90 -43
  79. data/lib/action_dispatch/journey/gtg/builder.rb +28 -41
  80. data/lib/action_dispatch/journey/gtg/simulator.rb +11 -16
  81. data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -21
  82. data/lib/action_dispatch/journey/nfa/dot.rb +3 -14
  83. data/lib/action_dispatch/journey/nodes/node.rb +29 -15
  84. data/lib/action_dispatch/journey/parser.rb +17 -16
  85. data/lib/action_dispatch/journey/parser.y +4 -3
  86. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  87. data/lib/action_dispatch/journey/path/pattern.rb +58 -54
  88. data/lib/action_dispatch/journey/route.rb +100 -32
  89. data/lib/action_dispatch/journey/router/utils.rb +29 -18
  90. data/lib/action_dispatch/journey/router.rb +55 -51
  91. data/lib/action_dispatch/journey/routes.rb +17 -17
  92. data/lib/action_dispatch/journey/scanner.rb +26 -17
  93. data/lib/action_dispatch/journey/visitors.rb +98 -54
  94. data/lib/action_dispatch/journey.rb +5 -5
  95. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  96. data/lib/action_dispatch/middleware/callbacks.rb +3 -6
  97. data/lib/action_dispatch/middleware/cookies.rb +347 -217
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +135 -63
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  101. data/lib/action_dispatch/middleware/exception_wrapper.rb +115 -71
  102. data/lib/action_dispatch/middleware/executor.rb +21 -0
  103. data/lib/action_dispatch/middleware/flash.rb +78 -54
  104. data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
  105. data/lib/action_dispatch/middleware/public_exceptions.rb +32 -27
  106. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  107. data/lib/action_dispatch/middleware/remote_ip.rb +53 -45
  108. data/lib/action_dispatch/middleware/request_id.rb +17 -10
  109. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -26
  110. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  111. data/lib/action_dispatch/middleware/session/cookie_store.rb +74 -75
  112. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  113. data/lib/action_dispatch/middleware/show_exceptions.rb +28 -23
  114. data/lib/action_dispatch/middleware/ssl.rb +118 -35
  115. data/lib/action_dispatch/middleware/stack.rb +82 -41
  116. data/lib/action_dispatch/middleware/static.rb +156 -89
  117. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -14
  121. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +4 -2
  123. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  125. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  129. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +105 -8
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  135. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  137. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  138. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  139. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  140. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +87 -64
  141. data/lib/action_dispatch/railtie.rb +27 -13
  142. data/lib/action_dispatch/request/session.rb +109 -61
  143. data/lib/action_dispatch/request/utils.rb +90 -23
  144. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  145. data/lib/action_dispatch/routing/inspector.rb +141 -102
  146. data/lib/action_dispatch/routing/mapper.rb +811 -473
  147. data/lib/action_dispatch/routing/polymorphic_routes.rb +167 -143
  148. data/lib/action_dispatch/routing/redirection.rb +37 -27
  149. data/lib/action_dispatch/routing/route_set.rb +363 -331
  150. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  151. data/lib/action_dispatch/routing/url_for.rb +66 -26
  152. data/lib/action_dispatch/routing.rb +36 -36
  153. data/lib/action_dispatch/system_test_case.rb +190 -0
  154. data/lib/action_dispatch/system_testing/browser.rb +86 -0
  155. data/lib/action_dispatch/system_testing/driver.rb +67 -0
  156. data/lib/action_dispatch/system_testing/server.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +138 -0
  158. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +29 -0
  159. data/lib/action_dispatch/testing/assertion_response.rb +46 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +44 -22
  161. data/lib/action_dispatch/testing/assertions/routing.rb +47 -31
  162. data/lib/action_dispatch/testing/assertions.rb +6 -4
  163. data/lib/action_dispatch/testing/integration.rb +391 -220
  164. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  165. data/lib/action_dispatch/testing/test_process.rb +53 -22
  166. data/lib/action_dispatch/testing/test_request.rb +27 -34
  167. data/lib/action_dispatch/testing/test_response.rb +11 -11
  168. data/lib/action_dispatch.rb +35 -21
  169. data/lib/action_pack/gem_version.rb +6 -4
  170. data/lib/action_pack/version.rb +3 -1
  171. data/lib/action_pack.rb +4 -2
  172. metadata +78 -48
  173. data/lib/action_controller/metal/force_ssl.rb +0 -97
  174. data/lib/action_controller/metal/hide_actions.rb +0 -40
  175. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  176. data/lib/action_controller/middleware.rb +0 -39
  177. data/lib/action_controller/model_naming.rb +0 -12
  178. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  179. data/lib/action_dispatch/journey/backwards.rb +0 -5
  180. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  181. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  182. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  183. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  184. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  185. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  186. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  187. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,27 +1,54 @@
1
- require 'active_support/core_ext/hash/indifferent_access'
2
- require 'active_support/core_ext/array/wrap'
3
- require 'active_support/core_ext/string/filters'
4
- require 'active_support/deprecation'
5
- require 'active_support/rescuable'
6
- require 'action_dispatch/http/upload'
7
- require 'stringio'
8
- require 'set'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+ require "active_support/core_ext/array/wrap"
5
+ require "active_support/core_ext/string/filters"
6
+ require "active_support/core_ext/object/to_query"
7
+ require "action_dispatch/http/upload"
8
+ require "rack/test"
9
+ require "stringio"
10
+ require "set"
11
+ require "yaml"
9
12
 
10
13
  module ActionController
11
14
  # Raised when a required parameter is missing.
12
15
  #
13
16
  # params = ActionController::Parameters.new(a: {})
14
17
  # params.fetch(:b)
15
- # # => ActionController::ParameterMissing: param not found: b
18
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: b
16
19
  # params.require(:a)
17
- # # => ActionController::ParameterMissing: param not found: a
20
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: a
18
21
  class ParameterMissing < KeyError
19
- attr_reader :param # :nodoc:
22
+ attr_reader :param, :keys # :nodoc:
20
23
 
21
- def initialize(param) # :nodoc:
24
+ def initialize(param, keys = nil) # :nodoc:
22
25
  @param = param
26
+ @keys = keys
23
27
  super("param is missing or the value is empty: #{param}")
24
28
  end
29
+
30
+ class Correction
31
+ def initialize(error)
32
+ @error = error
33
+ end
34
+
35
+ def corrections
36
+ if @error.param && @error.keys
37
+ maybe_these = @error.keys
38
+
39
+ maybe_these.sort_by { |n|
40
+ DidYouMean::Jaro.distance(@error.param.to_s, n)
41
+ }.reverse.first(4)
42
+ else
43
+ []
44
+ end
45
+ end
46
+ end
47
+
48
+ # We may not have DYM, and DYM might not let us register error handlers
49
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
50
+ DidYouMean.correct_error(self, Correction)
51
+ end
25
52
  end
26
53
 
27
54
  # Raised when a supplied parameter is not expected and
@@ -30,19 +57,31 @@ module ActionController
30
57
  #
31
58
  # params = ActionController::Parameters.new(a: "123", b: "456")
32
59
  # params.permit(:c)
33
- # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b
60
+ # # => ActionController::UnpermittedParameters: found unpermitted parameters: :a, :b
34
61
  class UnpermittedParameters < IndexError
35
62
  attr_reader :params # :nodoc:
36
63
 
37
64
  def initialize(params) # :nodoc:
38
65
  @params = params
39
- super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(", ")}")
66
+ super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.map { |e| ":#{e}" }.join(", ")}")
67
+ end
68
+ end
69
+
70
+ # Raised when a Parameters instance is not marked as permitted and
71
+ # an operation to transform it to hash is called.
72
+ #
73
+ # params = ActionController::Parameters.new(a: "123", b: "456")
74
+ # params.to_h
75
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
76
+ class UnfilteredParameters < ArgumentError
77
+ def initialize # :nodoc:
78
+ super("unable to convert unpermitted parameters to hash")
40
79
  end
41
80
  end
42
81
 
43
82
  # == Action Controller \Parameters
44
83
  #
45
- # Allows to choose which attributes should be whitelisted for mass updating
84
+ # Allows you to choose which attributes should be permitted for mass updating
46
85
  # and thus prevent accidentally exposing that which shouldn't be exposed.
47
86
  # Provides two methods for this purpose: #require and #permit. The former is
48
87
  # used to mark parameters as required. The latter is used to set the parameter
@@ -50,15 +89,14 @@ module ActionController
50
89
  #
51
90
  # params = ActionController::Parameters.new({
52
91
  # person: {
53
- # name: 'Francesco',
92
+ # name: "Francesco",
54
93
  # age: 22,
55
- # role: 'admin'
94
+ # role: "admin"
56
95
  # }
57
96
  # })
58
97
  #
59
98
  # permitted = params.require(:person).permit(:name, :age)
60
- # permitted # => {"name"=>"Francesco", "age"=>22}
61
- # permitted.class # => ActionController::Parameters
99
+ # permitted # => <ActionController::Parameters {"name"=>"Francesco", "age"=>22} permitted: true>
62
100
  # permitted.permitted? # => true
63
101
  #
64
102
  # Person.first.update!(permitted)
@@ -69,8 +107,8 @@ module ActionController
69
107
  # * +permit_all_parameters+ - If it's +true+, all the parameters will be
70
108
  # permitted by default. The default is +false+.
71
109
  # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters
72
- # that are not explicitly permitted are found. The values can be <tt>:log</tt> to
73
- # write a message on the logger or <tt>:raise</tt> to raise
110
+ # that are not explicitly permitted are found. The values can be +false+ to just filter them
111
+ # out, <tt>:log</tt> to additionally write a message on the logger, or <tt>:raise</tt> to raise
74
112
  # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
75
113
  # in test and development environments, +false+ otherwise.
76
114
  #
@@ -86,7 +124,7 @@ module ActionController
86
124
  #
87
125
  # params = ActionController::Parameters.new(a: "123", b: "456")
88
126
  # params.permit(:c)
89
- # # => {}
127
+ # # => <ActionController::Parameters {} permitted: true>
90
128
  #
91
129
  # ActionController::Parameters.action_on_unpermitted_parameters = :raise
92
130
  #
@@ -98,35 +136,129 @@ module ActionController
98
136
  # environment they should only be set once at boot-time and never mutated at
99
137
  # runtime.
100
138
  #
101
- # <tt>ActionController::Parameters</tt> inherits from
102
- # <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
103
- # that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
139
+ # You can fetch values of <tt>ActionController::Parameters</tt> using either
140
+ # <tt>:key</tt> or <tt>"key"</tt>.
104
141
  #
105
- # params = ActionController::Parameters.new(key: 'value')
142
+ # params = ActionController::Parameters.new(key: "value")
106
143
  # params[:key] # => "value"
107
144
  # params["key"] # => "value"
108
- class Parameters < ActiveSupport::HashWithIndifferentAccess
109
- cattr_accessor :permit_all_parameters, instance_accessor: false
145
+ class Parameters
146
+ cattr_accessor :permit_all_parameters, instance_accessor: false, default: false
147
+
110
148
  cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
111
149
 
150
+ ##
151
+ # :method: as_json
152
+ #
153
+ # :call-seq:
154
+ # as_json(options=nil)
155
+ #
156
+ # Returns a hash that can be used as the JSON representation for the parameters.
157
+
158
+ ##
159
+ # :method: each_key
160
+ #
161
+ # :call-seq:
162
+ # each_key()
163
+ #
164
+ # Calls block once for each key in the parameters, passing the key.
165
+ # If no block is given, an enumerator is returned instead.
166
+
167
+ ##
168
+ # :method: empty?
169
+ #
170
+ # :call-seq:
171
+ # empty?()
172
+ #
173
+ # Returns true if the parameters have no key/value pairs.
174
+
175
+ ##
176
+ # :method: has_key?
177
+ #
178
+ # :call-seq:
179
+ # has_key?(key)
180
+ #
181
+ # Returns true if the given key is present in the parameters.
182
+
183
+ ##
184
+ # :method: has_value?
185
+ #
186
+ # :call-seq:
187
+ # has_value?(value)
188
+ #
189
+ # Returns true if the given value is present for some key in the parameters.
190
+
191
+ ##
192
+ # :method: include?
193
+ #
194
+ # :call-seq:
195
+ # include?(key)
196
+ #
197
+ # Returns true if the given key is present in the parameters.
198
+
199
+ ##
200
+ # :method: key?
201
+ #
202
+ # :call-seq:
203
+ # key?(key)
204
+ #
205
+ # Returns true if the given key is present in the parameters.
206
+
207
+ ##
208
+ # :method: member?
209
+ #
210
+ # :call-seq:
211
+ # member?(key)
212
+ #
213
+ # Returns true if the given key is present in the parameters.
214
+
215
+ ##
216
+ # :method: keys
217
+ #
218
+ # :call-seq:
219
+ # keys()
220
+ #
221
+ # Returns a new array of the keys of the parameters.
222
+
223
+ ##
224
+ # :method: to_s
225
+ #
226
+ # :call-seq:
227
+ # to_s()
228
+ #
229
+ # Returns the content of the parameters as a string.
230
+
231
+ ##
232
+ # :method: value?
233
+ #
234
+ # :call-seq:
235
+ # value?(value)
236
+ #
237
+ # Returns true if the given value is present for some key in the parameters.
238
+
239
+ ##
240
+ # :method: values
241
+ #
242
+ # :call-seq:
243
+ # values()
244
+ #
245
+ # Returns a new array of the values of the parameters.
246
+ delegate :keys, :key?, :has_key?, :member?, :values, :has_value?, :value?, :empty?, :include?,
247
+ :as_json, :to_s, :each_key, to: :@parameters
248
+
112
249
  # By default, never raise an UnpermittedParameters exception if these
113
250
  # params are present. The default includes both 'controller' and 'action'
114
251
  # because they are added by Rails and should be of no concern. One way
115
252
  # to change these is to specify `always_permitted_parameters` in your
116
253
  # config. For instance:
117
254
  #
118
- # config.always_permitted_parameters = %w( controller action format )
119
- cattr_accessor :always_permitted_parameters
120
- self.always_permitted_parameters = %w( controller action )
255
+ # config.action_controller.always_permitted_parameters = %w( controller action format )
256
+ cattr_accessor :always_permitted_parameters, default: %w( controller action )
121
257
 
122
- def self.const_missing(const_name)
123
- super unless const_name == :NEVER_UNPERMITTED_PARAMS
124
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
125
- `ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
126
- Use `ActionController::Parameters.always_permitted_parameters` instead.
127
- MSG
128
-
129
- always_permitted_parameters
258
+ class << self
259
+ def nested_attribute?(key, value) # :nodoc:
260
+ /\A-?\d+\z/.match?(key) && (value.is_a?(Hash) || value.is_a?(Parameters))
261
+ end
130
262
  end
131
263
 
132
264
  # Returns a new instance of <tt>ActionController::Parameters</tt>.
@@ -136,57 +268,142 @@ module ActionController
136
268
  # class Person < ActiveRecord::Base
137
269
  # end
138
270
  #
139
- # params = ActionController::Parameters.new(name: 'Francesco')
271
+ # params = ActionController::Parameters.new(name: "Francesco")
140
272
  # params.permitted? # => false
141
273
  # Person.new(params) # => ActiveModel::ForbiddenAttributesError
142
274
  #
143
275
  # ActionController::Parameters.permit_all_parameters = true
144
276
  #
145
- # params = ActionController::Parameters.new(name: 'Francesco')
277
+ # params = ActionController::Parameters.new(name: "Francesco")
146
278
  # params.permitted? # => true
147
279
  # Person.new(params) # => #<Person id: nil, name: "Francesco">
148
- def initialize(attributes = nil)
149
- super(attributes)
280
+ def initialize(parameters = {})
281
+ @parameters = parameters.with_indifferent_access
150
282
  @permitted = self.class.permit_all_parameters
151
283
  end
152
284
 
153
- # Returns a safe +Hash+ representation of this parameter with all
154
- # unpermitted keys removed.
285
+ # Returns true if another +Parameters+ object contains the same content and
286
+ # permitted flag.
287
+ def ==(other)
288
+ if other.respond_to?(:permitted?)
289
+ permitted? == other.permitted? && parameters == other.parameters
290
+ else
291
+ @parameters == other
292
+ end
293
+ end
294
+ alias eql? ==
295
+
296
+ def hash
297
+ [@parameters.hash, @permitted].hash
298
+ end
299
+
300
+ # Returns a safe <tt>ActiveSupport::HashWithIndifferentAccess</tt>
301
+ # representation of the parameters with all unpermitted keys removed.
155
302
  #
156
303
  # params = ActionController::Parameters.new({
157
- # name: 'Senjougahara Hitagi',
158
- # oddity: 'Heavy stone crab'
304
+ # name: "Senjougahara Hitagi",
305
+ # oddity: "Heavy stone crab"
159
306
  # })
160
- # params.to_h # => {}
307
+ # params.to_h
308
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
161
309
  #
162
310
  # safe_params = params.permit(:name)
163
311
  # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
164
312
  def to_h
165
313
  if permitted?
166
- to_hash
314
+ convert_parameters_to_hashes(@parameters, :to_h)
167
315
  else
168
- slice(*self.class.always_permitted_parameters).permit!.to_h
316
+ raise UnfilteredParameters
169
317
  end
170
318
  end
171
319
 
172
- # Returns an unsafe, unfiltered +Hash+ representation of this parameter.
320
+ # Returns a safe <tt>Hash</tt> representation of the parameters
321
+ # with all unpermitted keys removed.
322
+ #
323
+ # params = ActionController::Parameters.new({
324
+ # name: "Senjougahara Hitagi",
325
+ # oddity: "Heavy stone crab"
326
+ # })
327
+ # params.to_hash
328
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
329
+ #
330
+ # safe_params = params.permit(:name)
331
+ # safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"}
332
+ def to_hash
333
+ to_h.to_hash
334
+ end
335
+
336
+ # Returns a string representation of the receiver suitable for use as a URL
337
+ # query string:
338
+ #
339
+ # params = ActionController::Parameters.new({
340
+ # name: "David",
341
+ # nationality: "Danish"
342
+ # })
343
+ # params.to_query
344
+ # # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash
345
+ #
346
+ # safe_params = params.permit(:name, :nationality)
347
+ # safe_params.to_query
348
+ # # => "name=David&nationality=Danish"
349
+ #
350
+ # An optional namespace can be passed to enclose key names:
351
+ #
352
+ # params = ActionController::Parameters.new({
353
+ # name: "David",
354
+ # nationality: "Danish"
355
+ # })
356
+ # safe_params = params.permit(:name, :nationality)
357
+ # safe_params.to_query("user")
358
+ # # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish"
359
+ #
360
+ # The string pairs "key=value" that conform the query string
361
+ # are sorted lexicographically in ascending order.
362
+ #
363
+ # This method is also aliased as +to_param+.
364
+ def to_query(*args)
365
+ to_h.to_query(*args)
366
+ end
367
+ alias_method :to_param, :to_query
368
+
369
+ # Returns an unsafe, unfiltered
370
+ # <tt>ActiveSupport::HashWithIndifferentAccess</tt> representation of the
371
+ # parameters.
372
+ #
373
+ # params = ActionController::Parameters.new({
374
+ # name: "Senjougahara Hitagi",
375
+ # oddity: "Heavy stone crab"
376
+ # })
377
+ # params.to_unsafe_h
378
+ # # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"}
173
379
  def to_unsafe_h
174
- to_hash
380
+ convert_parameters_to_hashes(@parameters, :to_unsafe_h)
175
381
  end
176
382
  alias_method :to_unsafe_hash, :to_unsafe_h
177
383
 
178
- # Convert all hashes in values into parameters, then yield each pair like
179
- # the same way as <tt>Hash#each_pair</tt>
384
+ # Convert all hashes in values into parameters, then yield each pair in
385
+ # the same way as <tt>Hash#each_pair</tt>.
180
386
  def each_pair(&block)
181
- super do |key, value|
182
- convert_hashes_to_parameters(key, value)
387
+ return to_enum(__callee__) unless block_given?
388
+ @parameters.each_pair do |key, value|
389
+ yield [key, convert_hashes_to_parameters(key, value)]
183
390
  end
184
391
 
185
- super
392
+ self
186
393
  end
187
-
188
394
  alias_method :each, :each_pair
189
395
 
396
+ # Convert all hashes in values into parameters, then yield each value in
397
+ # the same way as <tt>Hash#each_value</tt>.
398
+ def each_value(&block)
399
+ return to_enum(:each_value) unless block_given?
400
+ @parameters.each_pair do |key, value|
401
+ yield convert_hashes_to_parameters(key, value)
402
+ end
403
+
404
+ self
405
+ end
406
+
190
407
  # Attribute that keeps track of converted arrays, if any, to avoid double
191
408
  # looping in the common use case permit + mass-assignment. Defined in a
192
409
  # method to instantiate it only if needed.
@@ -214,7 +431,7 @@ module ActionController
214
431
  # class Person < ActiveRecord::Base
215
432
  # end
216
433
  #
217
- # params = ActionController::Parameters.new(name: 'Francesco')
434
+ # params = ActionController::Parameters.new(name: "Francesco")
218
435
  # params.permitted? # => false
219
436
  # Person.new(params) # => ActiveModel::ForbiddenAttributesError
220
437
  # params.permit!
@@ -222,7 +439,7 @@ module ActionController
222
439
  # Person.new(params) # => #<Person id: nil, name: "Francesco">
223
440
  def permit!
224
441
  each_pair do |key, value|
225
- Array.wrap(value).each do |v|
442
+ Array.wrap(value).flatten.each do |v|
226
443
  v.permit! if v.respond_to? :permit!
227
444
  end
228
445
  end
@@ -231,24 +448,63 @@ module ActionController
231
448
  self
232
449
  end
233
450
 
234
- # Ensures that a parameter is present. If it's present, returns
235
- # the parameter at the given +key+, otherwise raises an
236
- # <tt>ActionController::ParameterMissing</tt> error.
451
+ # This method accepts both a single key and an array of keys.
452
+ #
453
+ # When passed a single key, if it exists and its associated value is
454
+ # either present or the singleton +false+, returns said value:
237
455
  #
238
- # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
239
- # # => {"name"=>"Francesco"}
456
+ # ActionController::Parameters.new(person: { name: "Francesco" }).require(:person)
457
+ # # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
458
+ #
459
+ # Otherwise raises <tt>ActionController::ParameterMissing</tt>:
460
+ #
461
+ # ActionController::Parameters.new.require(:person)
462
+ # # ActionController::ParameterMissing: param is missing or the value is empty: person
240
463
  #
241
464
  # ActionController::Parameters.new(person: nil).require(:person)
242
- # # => ActionController::ParameterMissing: param not found: person
465
+ # # ActionController::ParameterMissing: param is missing or the value is empty: person
466
+ #
467
+ # ActionController::Parameters.new(person: "\t").require(:person)
468
+ # # ActionController::ParameterMissing: param is missing or the value is empty: person
243
469
  #
244
470
  # ActionController::Parameters.new(person: {}).require(:person)
245
- # # => ActionController::ParameterMissing: param not found: person
471
+ # # ActionController::ParameterMissing: param is missing or the value is empty: person
472
+ #
473
+ # When given an array of keys, the method tries to require each one of them
474
+ # in order. If it succeeds, an array with the respective return values is
475
+ # returned:
476
+ #
477
+ # params = ActionController::Parameters.new(user: { ... }, profile: { ... })
478
+ # user_params, profile_params = params.require([:user, :profile])
479
+ #
480
+ # Otherwise, the method re-raises the first exception found:
481
+ #
482
+ # params = ActionController::Parameters.new(user: {}, profile: {})
483
+ # user_params, profile_params = params.require([:user, :profile])
484
+ # # ActionController::ParameterMissing: param is missing or the value is empty: user
485
+ #
486
+ # Technically this method can be used to fetch terminal values:
487
+ #
488
+ # # CAREFUL
489
+ # params = ActionController::Parameters.new(person: { name: "Finn" })
490
+ # name = params.require(:person).require(:name) # CAREFUL
491
+ #
492
+ # but take into account that at some point those ones have to be permitted:
493
+ #
494
+ # def person_params
495
+ # params.require(:person).permit(:name).tap do |person_params|
496
+ # person_params.require(:name) # SAFER
497
+ # end
498
+ # end
499
+ #
500
+ # for example.
246
501
  def require(key)
502
+ return key.map { |k| require(k) } if key.is_a?(Array)
247
503
  value = self[key]
248
504
  if value.present? || value == false
249
505
  value
250
506
  else
251
- raise ParameterMissing.new(key)
507
+ raise ParameterMissing.new(key, @parameters.keys)
252
508
  end
253
509
  end
254
510
 
@@ -260,7 +516,7 @@ module ActionController
260
516
  # for the object to +true+. This is useful for limiting which attributes
261
517
  # should be allowed for mass updating.
262
518
  #
263
- # params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
519
+ # params = ActionController::Parameters.new(user: { name: "Francesco", age: 22, role: "admin" })
264
520
  # permitted = params.require(:user).permit(:name, :age)
265
521
  # permitted.permitted? # => true
266
522
  # permitted.has_key?(:name) # => true
@@ -271,7 +527,7 @@ module ActionController
271
527
  #
272
528
  # params.permit(:name)
273
529
  #
274
- # +:name+ passes it is a key of +params+ whose associated value is of type
530
+ # +:name+ passes if it is a key of +params+ whose associated value is of type
275
531
  # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
276
532
  # +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
277
533
  # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
@@ -280,18 +536,27 @@ module ActionController
280
536
  # You may declare that the parameter should be an array of permitted scalars
281
537
  # by mapping it to an empty array:
282
538
  #
283
- # params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
539
+ # params = ActionController::Parameters.new(tags: ["rails", "parameters"])
284
540
  # params.permit(tags: [])
285
541
  #
542
+ # Sometimes it is not possible or convenient to declare the valid keys of
543
+ # a hash parameter or its internal structure. Just map to an empty hash:
544
+ #
545
+ # params.permit(preferences: {})
546
+ #
547
+ # Be careful because this opens the door to arbitrary input. In this
548
+ # case, +permit+ ensures values in the returned structure are permitted
549
+ # scalars and filters out anything else.
550
+ #
286
551
  # You can also use +permit+ on nested parameters, like:
287
552
  #
288
553
  # params = ActionController::Parameters.new({
289
554
  # person: {
290
- # name: 'Francesco',
555
+ # name: "Francesco",
291
556
  # age: 22,
292
557
  # pets: [{
293
- # name: 'Purplish',
294
- # category: 'dogs'
558
+ # name: "Purplish",
559
+ # category: "dogs"
295
560
  # }]
296
561
  # }
297
562
  # })
@@ -305,25 +570,25 @@ module ActionController
305
570
  #
306
571
  # Note that if you use +permit+ in a key that points to a hash,
307
572
  # it won't allow all the hash. You also need to specify which
308
- # attributes inside the hash should be whitelisted.
573
+ # attributes inside the hash should be permitted.
309
574
  #
310
575
  # params = ActionController::Parameters.new({
311
576
  # person: {
312
577
  # contact: {
313
- # email: 'none@test.com',
314
- # phone: '555-1234'
578
+ # email: "none@test.com",
579
+ # phone: "555-1234"
315
580
  # }
316
581
  # }
317
582
  # })
318
583
  #
319
584
  # params.require(:person).permit(:contact)
320
- # # => {}
585
+ # # => <ActionController::Parameters {} permitted: true>
321
586
  #
322
587
  # params.require(:person).permit(contact: :phone)
323
- # # => {"contact"=>{"phone"=>"555-1234"}}
588
+ # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"phone"=>"555-1234"} permitted: true>} permitted: true>
324
589
  #
325
590
  # params.require(:person).permit(contact: [ :email, :phone ])
326
- # # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
591
+ # # => <ActionController::Parameters {"contact"=><ActionController::Parameters {"email"=>"none@test.com", "phone"=>"555-1234"} permitted: true>} permitted: true>
327
592
  def permit(*filters)
328
593
  params = self.class.new
329
594
 
@@ -331,7 +596,7 @@ module ActionController
331
596
  case filter
332
597
  when Symbol, String
333
598
  permitted_scalar_filter(params, filter)
334
- when Hash then
599
+ when Hash
335
600
  hash_filter(params, filter)
336
601
  end
337
602
  end
@@ -344,28 +609,56 @@ module ActionController
344
609
  # Returns a parameter for the given +key+. If not found,
345
610
  # returns +nil+.
346
611
  #
347
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
348
- # params[:person] # => {"name"=>"Francesco"}
612
+ # params = ActionController::Parameters.new(person: { name: "Francesco" })
613
+ # params[:person] # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
349
614
  # params[:none] # => nil
350
615
  def [](key)
351
- convert_hashes_to_parameters(key, super)
616
+ convert_hashes_to_parameters(key, @parameters[key])
617
+ end
618
+
619
+ # Assigns a value to a given +key+. The given key may still get filtered out
620
+ # when +permit+ is called.
621
+ def []=(key, value)
622
+ @parameters[key] = value
352
623
  end
353
624
 
354
625
  # Returns a parameter for the given +key+. If the +key+
355
626
  # can't be found, there are several options: With no other arguments,
356
627
  # it will raise an <tt>ActionController::ParameterMissing</tt> error;
357
- # if more arguments are given, then that will be returned; if a block
628
+ # if a second argument is given, then that is returned (converted to an
629
+ # instance of ActionController::Parameters if possible); if a block
358
630
  # is given, then that will be run and its result returned.
359
631
  #
360
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
361
- # params.fetch(:person) # => {"name"=>"Francesco"}
362
- # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
363
- # params.fetch(:none, 'Francesco') # => "Francesco"
364
- # params.fetch(:none) { 'Francesco' } # => "Francesco"
632
+ # params = ActionController::Parameters.new(person: { name: "Francesco" })
633
+ # params.fetch(:person) # => <ActionController::Parameters {"name"=>"Francesco"} permitted: false>
634
+ # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
635
+ # params.fetch(:none, {}) # => <ActionController::Parameters {} permitted: false>
636
+ # params.fetch(:none, "Francesco") # => "Francesco"
637
+ # params.fetch(:none) { "Francesco" } # => "Francesco"
365
638
  def fetch(key, *args)
366
- convert_hashes_to_parameters(key, super, false)
367
- rescue KeyError
368
- raise ActionController::ParameterMissing.new(key)
639
+ convert_value_to_parameters(
640
+ @parameters.fetch(key) {
641
+ if block_given?
642
+ yield
643
+ else
644
+ args.fetch(0) { raise ActionController::ParameterMissing.new(key, @parameters.keys) }
645
+ end
646
+ }
647
+ )
648
+ end
649
+
650
+ # Extracts the nested parameter from the given +keys+ by calling +dig+
651
+ # at each step. Returns +nil+ if any intermediate step is +nil+.
652
+ #
653
+ # params = ActionController::Parameters.new(foo: { bar: { baz: 1 } })
654
+ # params.dig(:foo, :bar, :baz) # => 1
655
+ # params.dig(:foo, :zot, :xyz) # => nil
656
+ #
657
+ # params2 = ActionController::Parameters.new(foo: [10, 11, 12])
658
+ # params2.dig(:foo, 1) # => 11
659
+ def dig(*keys)
660
+ convert_hashes_to_parameters(keys.first, @parameters[keys.first])
661
+ @parameters.dig(*keys)
369
662
  end
370
663
 
371
664
  # Returns a new <tt>ActionController::Parameters</tt> instance that
@@ -373,19 +666,36 @@ module ActionController
373
666
  # don't exist, returns an empty hash.
374
667
  #
375
668
  # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
376
- # params.slice(:a, :b) # => {"a"=>1, "b"=>2}
377
- # params.slice(:d) # => {}
669
+ # params.slice(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
670
+ # params.slice(:d) # => <ActionController::Parameters {} permitted: false>
378
671
  def slice(*keys)
379
- new_instance_with_inherited_permitted_status(super)
672
+ new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
673
+ end
674
+
675
+ # Returns current <tt>ActionController::Parameters</tt> instance which
676
+ # contains only the given +keys+.
677
+ def slice!(*keys)
678
+ @parameters.slice!(*keys)
679
+ self
680
+ end
681
+
682
+ # Returns a new <tt>ActionController::Parameters</tt> instance that
683
+ # filters out the given +keys+.
684
+ #
685
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
686
+ # params.except(:a, :b) # => <ActionController::Parameters {"c"=>3} permitted: false>
687
+ # params.except(:d) # => <ActionController::Parameters {"a"=>1, "b"=>2, "c"=>3} permitted: false>
688
+ def except(*keys)
689
+ new_instance_with_inherited_permitted_status(@parameters.except(*keys))
380
690
  end
381
691
 
382
692
  # Removes and returns the key/value pairs matching the given keys.
383
693
  #
384
694
  # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
385
- # params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
386
- # params # => {"c"=>3}
695
+ # params.extract!(:a, :b) # => <ActionController::Parameters {"a"=>1, "b"=>2} permitted: false>
696
+ # params # => <ActionController::Parameters {"c"=>3} permitted: false>
387
697
  def extract!(*keys)
388
- new_instance_with_inherited_permitted_status(super)
698
+ new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
389
699
  end
390
700
 
391
701
  # Returns a new <tt>ActionController::Parameters</tt> with the results of
@@ -393,56 +703,208 @@ module ActionController
393
703
  #
394
704
  # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
395
705
  # params.transform_values { |x| x * 2 }
396
- # # => {"a"=>2, "b"=>4, "c"=>6}
706
+ # # => <ActionController::Parameters {"a"=>2, "b"=>4, "c"=>6} permitted: false>
397
707
  def transform_values
398
- if block_given?
399
- new_instance_with_inherited_permitted_status(super)
400
- else
401
- super
402
- end
708
+ return to_enum(:transform_values) unless block_given?
709
+ new_instance_with_inherited_permitted_status(
710
+ @parameters.transform_values { |v| yield convert_value_to_parameters(v) }
711
+ )
403
712
  end
404
713
 
405
- # This method is here only to make sure that the returned object has the
406
- # correct +permitted+ status. It should not matter since the parent of
407
- # this object is +HashWithIndifferentAccess+
408
- def transform_keys # :nodoc:
409
- if block_given?
410
- new_instance_with_inherited_permitted_status(super)
411
- else
412
- super
413
- end
714
+ # Performs values transformation and returns the altered
715
+ # <tt>ActionController::Parameters</tt> instance.
716
+ def transform_values!
717
+ return to_enum(:transform_values!) unless block_given?
718
+ @parameters.transform_values! { |v| yield convert_value_to_parameters(v) }
719
+ self
720
+ end
721
+
722
+ # Returns a new <tt>ActionController::Parameters</tt> instance with the
723
+ # results of running +block+ once for every key. The values are unchanged.
724
+ def transform_keys(&block)
725
+ return to_enum(:transform_keys) unless block_given?
726
+ new_instance_with_inherited_permitted_status(
727
+ @parameters.transform_keys(&block)
728
+ )
729
+ end
730
+
731
+ # Performs keys transformation and returns the altered
732
+ # <tt>ActionController::Parameters</tt> instance.
733
+ def transform_keys!(&block)
734
+ return to_enum(:transform_keys!) unless block_given?
735
+ @parameters.transform_keys!(&block)
736
+ self
737
+ end
738
+
739
+ # Returns a new <tt>ActionController::Parameters</tt> instance with the
740
+ # results of running +block+ once for every key. This includes the keys
741
+ # from the root hash and from all nested hashes and arrays. The values are unchanged.
742
+ def deep_transform_keys(&block)
743
+ new_instance_with_inherited_permitted_status(
744
+ @parameters.deep_transform_keys(&block)
745
+ )
746
+ end
747
+
748
+ # Returns the <tt>ActionController::Parameters</tt> instance changing its keys.
749
+ # This includes the keys from the root hash and from all nested hashes and arrays.
750
+ # The values are unchanged.
751
+ def deep_transform_keys!(&block)
752
+ @parameters.deep_transform_keys!(&block)
753
+ self
414
754
  end
415
755
 
416
- # Deletes and returns a key-value pair from +Parameters+ whose key is equal
417
- # to key. If the key is not found, returns the default value. If the
418
- # optional code block is given and the key is not found, pass in the key
419
- # and return the result of block.
756
+ # Deletes a key-value pair from +Parameters+ and returns the value. If
757
+ # +key+ is not found, returns +nil+ (or, with optional code block, yields
758
+ # +key+ and returns the result). Cf. +#extract!+, which returns the
759
+ # corresponding +ActionController::Parameters+ object.
420
760
  def delete(key, &block)
421
- convert_hashes_to_parameters(key, super, false)
761
+ convert_value_to_parameters(@parameters.delete(key, &block))
762
+ end
763
+
764
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with only
765
+ # items that the block evaluates to true.
766
+ def select(&block)
767
+ new_instance_with_inherited_permitted_status(@parameters.select(&block))
422
768
  end
423
769
 
424
- # Equivalent to Hash#keep_if, but returns nil if no changes were made.
770
+ # Equivalent to Hash#keep_if, but returns +nil+ if no changes were made.
425
771
  def select!(&block)
426
- convert_value_to_parameters(super)
772
+ @parameters.select!(&block)
773
+ self
427
774
  end
775
+ alias_method :keep_if, :select!
428
776
 
429
- # Returns an exact copy of the <tt>ActionController::Parameters</tt>
430
- # instance. +permitted+ state is kept on the duped object.
431
- #
432
- # params = ActionController::Parameters.new(a: 1)
433
- # params.permit!
434
- # params.permitted? # => true
435
- # copy_params = params.dup # => {"a"=>1}
436
- # copy_params.permitted? # => true
437
- def dup
438
- super.tap do |duplicate|
777
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with items
778
+ # that the block evaluates to true removed.
779
+ def reject(&block)
780
+ new_instance_with_inherited_permitted_status(@parameters.reject(&block))
781
+ end
782
+
783
+ # Removes items that the block evaluates to true and returns self.
784
+ def reject!(&block)
785
+ @parameters.reject!(&block)
786
+ self
787
+ end
788
+ alias_method :delete_if, :reject!
789
+
790
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with +nil+ values removed.
791
+ def compact
792
+ new_instance_with_inherited_permitted_status(@parameters.compact)
793
+ end
794
+
795
+ # Removes all +nil+ values in place and returns +self+, or +nil+ if no changes were made.
796
+ def compact!
797
+ self if @parameters.compact!
798
+ end
799
+
800
+ # Returns a new instance of <tt>ActionController::Parameters</tt> without the blank values.
801
+ # Uses Object#blank? for determining if a value is blank.
802
+ def compact_blank
803
+ reject { |_k, v| v.blank? }
804
+ end
805
+
806
+ # Removes all blank values in place and returns self.
807
+ # Uses Object#blank? for determining if a value is blank.
808
+ def compact_blank!
809
+ reject! { |_k, v| v.blank? }
810
+ end
811
+
812
+ # Returns values that were assigned to the given +keys+. Note that all the
813
+ # +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
814
+ def values_at(*keys)
815
+ convert_value_to_parameters(@parameters.values_at(*keys))
816
+ end
817
+
818
+ # Returns a new <tt>ActionController::Parameters</tt> with all keys from
819
+ # +other_hash+ merged into current hash.
820
+ def merge(other_hash)
821
+ new_instance_with_inherited_permitted_status(
822
+ @parameters.merge(other_hash.to_h)
823
+ )
824
+ end
825
+
826
+ # Returns current <tt>ActionController::Parameters</tt> instance with
827
+ # +other_hash+ merged into current hash.
828
+ def merge!(other_hash)
829
+ @parameters.merge!(other_hash.to_h)
830
+ self
831
+ end
832
+
833
+ # Returns a new <tt>ActionController::Parameters</tt> with all keys from
834
+ # current hash merged into +other_hash+.
835
+ def reverse_merge(other_hash)
836
+ new_instance_with_inherited_permitted_status(
837
+ other_hash.to_h.merge(@parameters)
838
+ )
839
+ end
840
+ alias_method :with_defaults, :reverse_merge
841
+
842
+ # Returns current <tt>ActionController::Parameters</tt> instance with
843
+ # current hash merged into +other_hash+.
844
+ def reverse_merge!(other_hash)
845
+ @parameters.merge!(other_hash.to_h) { |key, left, right| left }
846
+ self
847
+ end
848
+ alias_method :with_defaults!, :reverse_merge!
849
+
850
+ # This is required by ActiveModel attribute assignment, so that user can
851
+ # pass +Parameters+ to a mass assignment methods in a model. It should not
852
+ # matter as we are using +HashWithIndifferentAccess+ internally.
853
+ def stringify_keys # :nodoc:
854
+ dup
855
+ end
856
+
857
+ def inspect
858
+ "#<#{self.class} #{@parameters} permitted: #{@permitted}>"
859
+ end
860
+
861
+ def self.hook_into_yaml_loading # :nodoc:
862
+ # Wire up YAML format compatibility with Rails 4.2 and Psych 2.0.8 and 2.0.9+.
863
+ # Makes the YAML parser call `init_with` when it encounters the keys below
864
+ # instead of trying its own parsing routines.
865
+ YAML.load_tags["!ruby/hash-with-ivars:ActionController::Parameters"] = name
866
+ YAML.load_tags["!ruby/hash:ActionController::Parameters"] = name
867
+ end
868
+ hook_into_yaml_loading
869
+
870
+ def init_with(coder) # :nodoc:
871
+ case coder.tag
872
+ when "!ruby/hash:ActionController::Parameters"
873
+ # YAML 2.0.8's format where hash instance variables weren't stored.
874
+ @parameters = coder.map.with_indifferent_access
875
+ @permitted = false
876
+ when "!ruby/hash-with-ivars:ActionController::Parameters"
877
+ # YAML 2.0.9's Hash subclass format where keys and values
878
+ # were stored under an elements hash and `permitted` within an ivars hash.
879
+ @parameters = coder.map["elements"].with_indifferent_access
880
+ @permitted = coder.map["ivars"][:@permitted]
881
+ when "!ruby/object:ActionController::Parameters"
882
+ # YAML's Object format. Only needed because of the format
883
+ # backwards compatibility above, otherwise equivalent to YAML's initialization.
884
+ @parameters, @permitted = coder.map["parameters"], coder.map["permitted"]
885
+ end
886
+ end
887
+
888
+ # Returns duplicate of object including all parameters.
889
+ def deep_dup
890
+ self.class.new(@parameters.deep_dup).tap do |duplicate|
439
891
  duplicate.permitted = @permitted
440
892
  end
441
893
  end
442
894
 
443
895
  protected
444
- def permitted=(new_permitted)
445
- @permitted = new_permitted
896
+ attr_reader :parameters
897
+
898
+ attr_writer :permitted
899
+
900
+ def nested_attributes?
901
+ @parameters.any? { |k, v| Parameters.nested_attribute?(k, v) }
902
+ end
903
+
904
+ def each_nested_attribute
905
+ hash = self.class.new
906
+ self.each { |k, v| hash[k] = yield v if Parameters.nested_attribute?(k, v) }
907
+ hash
446
908
  end
447
909
 
448
910
  private
@@ -452,38 +914,52 @@ module ActionController
452
914
  end
453
915
  end
454
916
 
455
- def convert_hashes_to_parameters(key, value, assign_if_converted=true)
917
+ def convert_parameters_to_hashes(value, using)
918
+ case value
919
+ when Array
920
+ value.map { |v| convert_parameters_to_hashes(v, using) }
921
+ when Hash
922
+ value.transform_values do |v|
923
+ convert_parameters_to_hashes(v, using)
924
+ end.with_indifferent_access
925
+ when Parameters
926
+ value.send(using)
927
+ else
928
+ value
929
+ end
930
+ end
931
+
932
+ def convert_hashes_to_parameters(key, value)
456
933
  converted = convert_value_to_parameters(value)
457
- self[key] = converted if assign_if_converted && !converted.equal?(value)
934
+ @parameters[key] = converted unless converted.equal?(value)
458
935
  converted
459
936
  end
460
937
 
461
938
  def convert_value_to_parameters(value)
462
- if value.is_a?(Array) && !converted_arrays.member?(value)
939
+ case value
940
+ when Array
941
+ return value if converted_arrays.member?(value)
463
942
  converted = value.map { |_| convert_value_to_parameters(_) }
464
943
  converted_arrays << converted
465
944
  converted
466
- elsif value.is_a?(Parameters) || !value.is_a?(Hash)
467
- value
468
- else
945
+ when Hash
469
946
  self.class.new(value)
470
- end
471
- end
472
-
473
- def each_element(object)
474
- if object.is_a?(Array)
475
- object.map { |el| yield el }.compact
476
- elsif fields_for_style?(object)
477
- hash = object.class.new
478
- object.each { |k,v| hash[k] = yield v }
479
- hash
480
947
  else
481
- yield object
948
+ value
482
949
  end
483
950
  end
484
951
 
485
- def fields_for_style?(object)
486
- object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
952
+ def each_element(object, &block)
953
+ case object
954
+ when Array
955
+ object.grep(Parameters).map { |el| yield el }.compact
956
+ when Parameters
957
+ if object.nested_attributes?
958
+ object.each_nested_attribute(&block)
959
+ else
960
+ yield object
961
+ end
962
+ end
487
963
  end
488
964
 
489
965
  def unpermitted_parameters!(params)
@@ -500,14 +976,14 @@ module ActionController
500
976
  end
501
977
 
502
978
  def unpermitted_keys(params)
503
- self.keys - params.keys - self.always_permitted_parameters
979
+ keys - params.keys - always_permitted_parameters
504
980
  end
505
981
 
506
982
  #
507
983
  # --- Filtering ----------------------------------------------------------
508
984
  #
509
985
 
510
- # This is a white list of permitted scalar types that includes the ones
986
+ # This is a list of permitted scalar types that includes the ones
511
987
  # supported in XML and JSON requests.
512
988
  #
513
989
  # This list is in particular used to filter ordinary requests, String goes
@@ -531,71 +1007,125 @@ module ActionController
531
1007
  ]
532
1008
 
533
1009
  def permitted_scalar?(value)
534
- PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
1010
+ PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
535
1011
  end
536
1012
 
537
- def permitted_scalar_filter(params, key)
538
- if has_key?(key) && permitted_scalar?(self[key])
539
- params[key] = self[key]
1013
+ # Adds existing keys to the params if their values are scalar.
1014
+ #
1015
+ # For example:
1016
+ #
1017
+ # puts self.keys #=> ["zipcode(90210i)"]
1018
+ # params = {}
1019
+ #
1020
+ # permitted_scalar_filter(params, "zipcode")
1021
+ #
1022
+ # puts params.keys # => ["zipcode"]
1023
+ def permitted_scalar_filter(params, permitted_key)
1024
+ permitted_key = permitted_key.to_s
1025
+
1026
+ if has_key?(permitted_key) && permitted_scalar?(self[permitted_key])
1027
+ params[permitted_key] = self[permitted_key]
540
1028
  end
541
1029
 
542
- keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
543
- if permitted_scalar?(self[k])
544
- params[k] = self[k]
545
- end
1030
+ each_key do |key|
1031
+ next unless key =~ /\(\d+[if]?\)\z/
1032
+ next unless $~.pre_match == permitted_key
1033
+
1034
+ params[key] = self[key] if permitted_scalar?(self[key])
546
1035
  end
547
1036
  end
548
1037
 
549
1038
  def array_of_permitted_scalars?(value)
550
- if value.is_a?(Array)
551
- value.all? {|element| permitted_scalar?(element)}
1039
+ if value.is_a?(Array) && value.all? { |element| permitted_scalar?(element) }
1040
+ yield value
552
1041
  end
553
1042
  end
554
1043
 
555
- def array_of_permitted_scalars_filter(params, key)
556
- if has_key?(key) && array_of_permitted_scalars?(self[key])
557
- params[key] = self[key]
558
- end
1044
+ def non_scalar?(value)
1045
+ value.is_a?(Array) || value.is_a?(Parameters)
559
1046
  end
560
1047
 
561
1048
  EMPTY_ARRAY = []
1049
+ EMPTY_HASH = {}
562
1050
  def hash_filter(params, filter)
563
1051
  filter = filter.with_indifferent_access
564
1052
 
565
1053
  # Slicing filters out non-declared keys.
566
1054
  slice(*filter.keys).each do |key, value|
567
1055
  next unless value
1056
+ next unless has_key? key
568
1057
 
569
1058
  if filter[key] == EMPTY_ARRAY
570
1059
  # Declaration { comment_ids: [] }.
571
- array_of_permitted_scalars_filter(params, key)
572
- else
1060
+ array_of_permitted_scalars?(self[key]) do |val|
1061
+ params[key] = val
1062
+ end
1063
+ elsif filter[key] == EMPTY_HASH
1064
+ # Declaration { preferences: {} }.
1065
+ if value.is_a?(Parameters)
1066
+ params[key] = permit_any_in_parameters(value)
1067
+ end
1068
+ elsif non_scalar?(value)
573
1069
  # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
574
1070
  params[key] = each_element(value) do |element|
575
- if element.is_a?(Hash)
576
- element = self.class.new(element) unless element.respond_to?(:permit)
577
- element.permit(*Array.wrap(filter[key]))
578
- end
1071
+ element.permit(*Array.wrap(filter[key]))
579
1072
  end
580
1073
  end
581
1074
  end
582
1075
  end
1076
+
1077
+ def permit_any_in_parameters(params)
1078
+ self.class.new.tap do |sanitized|
1079
+ params.each do |key, value|
1080
+ case value
1081
+ when ->(v) { permitted_scalar?(v) }
1082
+ sanitized[key] = value
1083
+ when Array
1084
+ sanitized[key] = permit_any_in_array(value)
1085
+ when Parameters
1086
+ sanitized[key] = permit_any_in_parameters(value)
1087
+ else
1088
+ # Filter this one out.
1089
+ end
1090
+ end
1091
+ end
1092
+ end
1093
+
1094
+ def permit_any_in_array(array)
1095
+ [].tap do |sanitized|
1096
+ array.each do |element|
1097
+ case element
1098
+ when ->(e) { permitted_scalar?(e) }
1099
+ sanitized << element
1100
+ when Parameters
1101
+ sanitized << permit_any_in_parameters(element)
1102
+ else
1103
+ # Filter this one out.
1104
+ end
1105
+ end
1106
+ end
1107
+ end
1108
+
1109
+ def initialize_copy(source)
1110
+ super
1111
+ @parameters = @parameters.dup
1112
+ end
583
1113
  end
584
1114
 
585
1115
  # == Strong \Parameters
586
1116
  #
587
1117
  # It provides an interface for protecting attributes from end-user
588
1118
  # assignment. This makes Action Controller parameters forbidden
589
- # to be used in Active Model mass assignment until they have been
590
- # whitelisted.
1119
+ # to be used in Active Model mass assignment until they have been explicitly
1120
+ # enumerated.
591
1121
  #
592
1122
  # In addition, parameters can be marked as required and flow through a
593
- # predefined raise/rescue flow to end up as a 400 Bad Request with no
1123
+ # predefined raise/rescue flow to end up as a <tt>400 Bad Request</tt> with no
594
1124
  # effort.
595
1125
  #
596
1126
  # class PeopleController < ActionController::Base
597
1127
  # # Using "Person.create(params[:person])" would raise an
598
- # # ActiveModel::ForbiddenAttributes exception because it'd
1128
+ # # ActiveModel::ForbiddenAttributesError exception because it'd
599
1129
  # # be using mass assignment without an explicit permit step.
600
1130
  # # This is the recommended form:
601
1131
  # def create
@@ -603,7 +1133,7 @@ module ActionController
603
1133
  # end
604
1134
  #
605
1135
  # # This will pass with flying colors as long as there's a person key in the
606
- # # parameters, otherwise it'll raise an ActionController::MissingParameter
1136
+ # # parameters, otherwise it'll raise an ActionController::ParameterMissing
607
1137
  # # exception, which will get caught by ActionController::Base and turned
608
1138
  # # into a 400 Bad Request reply.
609
1139
  # def update
@@ -614,7 +1144,7 @@ module ActionController
614
1144
  #
615
1145
  # private
616
1146
  # # Using a private method to encapsulate the permissible parameters is
617
- # # just a good pattern since you'll be able to reuse the same permit
1147
+ # # a good pattern since you'll be able to reuse the same permit
618
1148
  # # list between create and update. Also, you can specialize this method
619
1149
  # # with per-user checking of permissible attributes.
620
1150
  # def person_params
@@ -623,7 +1153,8 @@ module ActionController
623
1153
  # end
624
1154
  #
625
1155
  # In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
626
- # will need to specify which nested attributes should be whitelisted.
1156
+ # will need to specify which nested attributes should be permitted. You might want
1157
+ # to allow +:id+ and +:_destroy+, see ActiveRecord::NestedAttributes for more information.
627
1158
  #
628
1159
  # class Person
629
1160
  # has_many :pets
@@ -640,19 +1171,16 @@ module ActionController
640
1171
  # private
641
1172
  #
642
1173
  # def person_params
643
- # # It's mandatory to specify the nested attributes that should be whitelisted.
1174
+ # # It's mandatory to specify the nested attributes that should be permitted.
644
1175
  # # If you use `permit` with just the key that points to the nested attributes hash,
645
1176
  # # it will return an empty hash.
646
- # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
1177
+ # params.require(:person).permit(:name, :age, pets_attributes: [ :id, :name, :category ])
647
1178
  # end
648
1179
  # end
649
1180
  #
650
1181
  # See ActionController::Parameters.require and ActionController::Parameters.permit
651
1182
  # for more information.
652
1183
  module StrongParameters
653
- extend ActiveSupport::Concern
654
- include ActiveSupport::Rescuable
655
-
656
1184
  # Returns a new ActionController::Parameters object that
657
1185
  # has been instantiated with the <tt>request.parameters</tt>.
658
1186
  def params