actionpack 4.2.11.1 → 5.2.4.3

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