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