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