actionpack 4.2.10 → 7.2.0.rc1

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