durable_parameters 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +853 -0
  4. data/Rakefile +29 -0
  5. data/app/params/account_params.rb.example +38 -0
  6. data/app/params/application_params.rb +16 -0
  7. data/lib/durable_parameters/adapters/hanami.rb +138 -0
  8. data/lib/durable_parameters/adapters/rage.rb +124 -0
  9. data/lib/durable_parameters/adapters/rails.rb +280 -0
  10. data/lib/durable_parameters/adapters/sinatra.rb +91 -0
  11. data/lib/durable_parameters/core/application_params.rb +334 -0
  12. data/lib/durable_parameters/core/configuration.rb +83 -0
  13. data/lib/durable_parameters/core/forbidden_attributes_protection.rb +48 -0
  14. data/lib/durable_parameters/core/parameters.rb +643 -0
  15. data/lib/durable_parameters/core/params_registry.rb +110 -0
  16. data/lib/durable_parameters/core.rb +15 -0
  17. data/lib/durable_parameters/log_subscriber.rb +34 -0
  18. data/lib/durable_parameters/railtie.rb +65 -0
  19. data/lib/durable_parameters/version.rb +7 -0
  20. data/lib/durable_parameters.rb +41 -0
  21. data/lib/generators/rails/USAGE +12 -0
  22. data/lib/generators/rails/durable_parameters_controller_generator.rb +17 -0
  23. data/lib/generators/rails/templates/controller.rb +94 -0
  24. data/lib/legacy/action_controller/application_params.rb +235 -0
  25. data/lib/legacy/action_controller/parameters.rb +524 -0
  26. data/lib/legacy/action_controller/params_registry.rb +108 -0
  27. data/lib/legacy/active_model/forbidden_attributes_protection.rb +40 -0
  28. data/test/action_controller_required_params_test.rb +36 -0
  29. data/test/action_controller_tainted_params_test.rb +29 -0
  30. data/test/active_model_mass_assignment_taint_protection_test.rb +25 -0
  31. data/test/application_params_array_test.rb +245 -0
  32. data/test/application_params_edge_cases_test.rb +361 -0
  33. data/test/application_params_test.rb +893 -0
  34. data/test/controller_generator_test.rb +31 -0
  35. data/test/core_parameters_test.rb +2376 -0
  36. data/test/durable_parameters_test.rb +115 -0
  37. data/test/enhanced_error_messages_test.rb +120 -0
  38. data/test/gemfiles/Gemfile.rails-3.0.x +14 -0
  39. data/test/gemfiles/Gemfile.rails-3.1.x +14 -0
  40. data/test/gemfiles/Gemfile.rails-3.2.x +14 -0
  41. data/test/log_on_unpermitted_params_test.rb +49 -0
  42. data/test/metadata_validation_test.rb +294 -0
  43. data/test/multi_parameter_attributes_test.rb +38 -0
  44. data/test/parameters_core_methods_test.rb +503 -0
  45. data/test/parameters_integration_test.rb +553 -0
  46. data/test/parameters_permit_test.rb +491 -0
  47. data/test/parameters_require_test.rb +9 -0
  48. data/test/parameters_taint_test.rb +98 -0
  49. data/test/params_registry_concurrency_test.rb +422 -0
  50. data/test/params_registry_test.rb +112 -0
  51. data/test/permit_by_model_test.rb +227 -0
  52. data/test/raise_on_unpermitted_params_test.rb +32 -0
  53. data/test/test_helper.rb +38 -0
  54. data/test/transform_params_edge_cases_test.rb +526 -0
  55. data/test/transformation_test.rb +360 -0
  56. metadata +223 -0
@@ -0,0 +1,29 @@
1
+ require 'test_helper'
2
+
3
+ class PeopleController < ActionController::Base
4
+ def create
5
+ render plain: params[:person].permitted? ? "untainted" : "tainted"
6
+ end
7
+
8
+ def create_with_permit
9
+ render plain: params[:person].permit(:name).permitted? ? "untainted" : "tainted"
10
+ end
11
+ end
12
+
13
+ class ActionControllerTaintedParamsTest < ActionController::TestCase
14
+ tests PeopleController
15
+
16
+ setup do
17
+ @routes = Rails.application.routes
18
+ end
19
+
20
+ def test_parameters_are_tainted
21
+ post :create, params: { :person => { :name => "Mjallo!" } }
22
+ assert_equal "tainted", response.body
23
+ end
24
+
25
+ def test_parameters_can_be_permitted_and_are_then_not_tainted
26
+ post :create_with_permit, params: { :person => { :name => "Mjallo!" } }
27
+ assert_equal "untainted", response.body
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class Person
4
+ include StrongParameters::Core::ForbiddenAttributesProtection
5
+
6
+ public :sanitize_for_mass_assignment
7
+ end
8
+
9
+ class ActiveModelMassUpdateProtectionTest < Minitest::Test
10
+ def test_forbidden_attributes_cannot_be_used_for_mass_updating
11
+ assert_raises(StrongParameters::Core::ForbiddenAttributes) do
12
+ Person.new.sanitize_for_mass_assignment(ActionController::Parameters.new(:a => "b"))
13
+ end
14
+ end
15
+
16
+ def test_permitted_attributes_can_be_used_for_mass_updating
17
+ result = Person.new.sanitize_for_mass_assignment(ActionController::Parameters.new(:a => "b").permit(:a))
18
+ assert_equal({ "a" => "b" }, result.to_h)
19
+ end
20
+
21
+ def test_regular_attributes_should_still_be_allowed
22
+ result = Person.new.sanitize_for_mass_assignment(:a => "b")
23
+ assert_equal({ :a => "b" }, result)
24
+ end
25
+ end
@@ -0,0 +1,245 @@
1
+ require 'test_helper'
2
+
3
+ class ApplicationParamsArrayTest < Minitest::Test
4
+ def setup
5
+ ActionController::ParamsRegistry.clear!
6
+ end
7
+
8
+ def teardown
9
+ ActionController::ParamsRegistry.clear!
10
+ end
11
+
12
+ # Test basic array support
13
+ def test_allow_array_attribute
14
+ test_class = Class.new(ActionController::ApplicationParams) do
15
+ allow :tags, array: true
16
+ end
17
+
18
+ attrs = test_class.permitted_attributes
19
+ assert_equal 1, attrs.length
20
+ assert_equal({ tags: [] }, attrs.first)
21
+ end
22
+
23
+ # Test scalar and array attributes together
24
+ def test_mixed_scalar_and_array_attributes
25
+ test_class = Class.new(ActionController::ApplicationParams) do
26
+ allow :name
27
+ allow :tags, array: true
28
+ allow :email
29
+ allow :categories, array: true
30
+ end
31
+
32
+ attrs = test_class.permitted_attributes
33
+ assert_equal 4, attrs.length
34
+ assert attrs.include?(:name)
35
+ assert attrs.include?(:email)
36
+ assert attrs.include?({ tags: [] })
37
+ assert attrs.include?({ categories: [] })
38
+ end
39
+
40
+ # Test array with action filters
41
+ def test_array_with_only_filter
42
+ test_class = Class.new(ActionController::ApplicationParams) do
43
+ allow :tags, array: true, only: :create
44
+ end
45
+
46
+ # With create action
47
+ attrs = test_class.permitted_attributes(action: :create)
48
+ assert_equal 1, attrs.length
49
+ assert_equal({ tags: [] }, attrs.first)
50
+
51
+ # With update action
52
+ attrs = test_class.permitted_attributes(action: :update)
53
+ assert_equal 0, attrs.length
54
+ end
55
+
56
+ # Test array with except filter
57
+ def test_array_with_except_filter
58
+ test_class = Class.new(ActionController::ApplicationParams) do
59
+ allow :tags, array: true, except: :destroy
60
+ end
61
+
62
+ # With create action
63
+ attrs = test_class.permitted_attributes(action: :create)
64
+ assert_equal 1, attrs.length
65
+ assert_equal({ tags: [] }, attrs.first)
66
+
67
+ # With destroy action
68
+ attrs = test_class.permitted_attributes(action: :destroy)
69
+ assert_equal 0, attrs.length
70
+ end
71
+
72
+ # Test array inheritance
73
+ def test_array_inheritance
74
+ parent_class = Class.new(ActionController::ApplicationParams) do
75
+ allow :tags, array: true
76
+ end
77
+
78
+ child_class = Class.new(parent_class) do
79
+ allow :categories, array: true
80
+ end
81
+
82
+ attrs = child_class.permitted_attributes
83
+ assert_equal 2, attrs.length
84
+ assert attrs.include?({ tags: [] })
85
+ assert attrs.include?({ categories: [] })
86
+ end
87
+
88
+ # Test array can be denied in child class
89
+ def test_deny_inherited_array
90
+ parent_class = Class.new(ActionController::ApplicationParams) do
91
+ allow :tags, array: true
92
+ allow :name
93
+ end
94
+
95
+ child_class = Class.new(parent_class) do
96
+ deny :tags
97
+ allow :email
98
+ end
99
+
100
+ attrs = child_class.permitted_attributes
101
+ assert_equal 2, attrs.length
102
+ assert attrs.include?(:name)
103
+ assert attrs.include?(:email)
104
+ assert !attrs.include?({ tags: [] })
105
+ end
106
+
107
+ # Test transform_params with array attributes
108
+ def test_transform_params_with_array
109
+ test_class = Class.new(ActionController::ApplicationParams) do
110
+ def self.name
111
+ 'ArticleParams'
112
+ end
113
+ allow :title
114
+ allow :tags, array: true
115
+ end
116
+
117
+ ActionController::ParamsRegistry.register('Article', test_class)
118
+
119
+ params = ActionController::Parameters.new(
120
+ article: {
121
+ title: 'Test Article',
122
+ tags: ['ruby', 'rails', 'testing'],
123
+ body: 'Should be filtered out'
124
+ }
125
+ )
126
+
127
+ permitted = params.require(:article).transform_params()
128
+
129
+ assert permitted.permitted?
130
+ assert_equal 'Test Article', permitted[:title]
131
+ assert_equal ['ruby', 'rails', 'testing'], permitted[:tags]
132
+ assert_nil permitted[:body]
133
+ end
134
+
135
+ # Test transform_params with empty array
136
+ def test_transform_params_with_empty_array
137
+ test_class = Class.new(ActionController::ApplicationParams) do
138
+ def self.name
139
+ 'ArticleParams'
140
+ end
141
+ allow :title
142
+ allow :tags, array: true
143
+ end
144
+
145
+ ActionController::ParamsRegistry.register('Article', test_class)
146
+
147
+ params = ActionController::Parameters.new(
148
+ article: {
149
+ title: 'Test Article',
150
+ tags: []
151
+ }
152
+ )
153
+
154
+ permitted = params.require(:article).transform_params()
155
+
156
+ assert permitted.permitted?
157
+ assert_equal 'Test Article', permitted[:title]
158
+ assert_equal [], permitted[:tags]
159
+ end
160
+
161
+ # Test transform_params filters arrays with non-scalar elements
162
+ def test_transform_params_filters_non_scalar_array_elements
163
+ test_class = Class.new(ActionController::ApplicationParams) do
164
+ def self.name
165
+ 'ArticleParams'
166
+ end
167
+ allow :title
168
+ allow :tags, array: true
169
+ end
170
+
171
+ ActionController::ParamsRegistry.register('Article', test_class)
172
+
173
+ params = ActionController::Parameters.new(
174
+ article: {
175
+ title: 'Test Article',
176
+ tags: ['valid', { invalid: 'hash' }, 'also_valid', ['nested', 'array']]
177
+ }
178
+ )
179
+
180
+ permitted = params.require(:article).transform_params()
181
+
182
+ assert permitted.permitted?
183
+ assert_equal 'Test Article', permitted[:title]
184
+ # Strong parameters filters out entire array if it contains non-scalar elements
185
+ assert_nil permitted[:tags]
186
+ end
187
+
188
+ # Test array attribute with additional_attrs
189
+ def test_array_with_additional_attrs
190
+ test_class = Class.new(ActionController::ApplicationParams) do
191
+ def self.name
192
+ 'ArticleParams'
193
+ end
194
+ allow :title
195
+ end
196
+
197
+ ActionController::ParamsRegistry.register('Article', test_class)
198
+
199
+ params = ActionController::Parameters.new(
200
+ article: {
201
+ title: 'Test Article',
202
+ tags: ['ruby', 'rails']
203
+ }
204
+ )
205
+
206
+ # Add tags as additional attribute with array syntax
207
+ permitted = params.require(:article).transform_params(additional_attrs: [{ tags: [] }])
208
+
209
+ assert permitted.permitted?
210
+ assert_equal 'Test Article', permitted[:title]
211
+ assert_equal ['ruby', 'rails'], permitted[:tags]
212
+ end
213
+
214
+ # Test multiple array attributes
215
+ def test_multiple_array_attributes
216
+ test_class = Class.new(ActionController::ApplicationParams) do
217
+ def self.name
218
+ 'ArticleParams'
219
+ end
220
+ allow :title
221
+ allow :tags, array: true
222
+ allow :categories, array: true
223
+ allow :author_ids, array: true
224
+ end
225
+
226
+ ActionController::ParamsRegistry.register('Article', test_class)
227
+
228
+ params = ActionController::Parameters.new(
229
+ article: {
230
+ title: 'Test Article',
231
+ tags: ['ruby', 'rails'],
232
+ categories: ['tech', 'programming'],
233
+ author_ids: [1, 2, 3]
234
+ }
235
+ )
236
+
237
+ permitted = params.require(:article).transform_params()
238
+
239
+ assert permitted.permitted?
240
+ assert_equal 'Test Article', permitted[:title]
241
+ assert_equal ['ruby', 'rails'], permitted[:tags]
242
+ assert_equal ['tech', 'programming'], permitted[:categories]
243
+ assert_equal [1, 2, 3], permitted[:author_ids]
244
+ end
245
+ end
@@ -0,0 +1,361 @@
1
+ require 'test_helper'
2
+
3
+ class ApplicationParamsEdgeCasesTest < Minitest::Test
4
+ def setup
5
+ ActionController::ParamsRegistry.clear!
6
+ end
7
+
8
+ def teardown
9
+ ActionController::ParamsRegistry.clear!
10
+ end
11
+
12
+ # Test duplicate allow calls
13
+ def test_duplicate_allow_calls_dont_duplicate_attributes
14
+ test_class = Class.new(ActionController::ApplicationParams) do
15
+ allow :name
16
+ allow :name
17
+ allow :name
18
+ end
19
+
20
+ assert_equal [:name], test_class.allowed_attributes
21
+ end
22
+
23
+ # Test duplicate deny calls
24
+ def test_duplicate_deny_calls_dont_duplicate_attributes
25
+ test_class = Class.new(ActionController::ApplicationParams) do
26
+ deny :admin
27
+ deny :admin
28
+ deny :admin
29
+ end
30
+
31
+ assert_equal [:admin], test_class.denied_attributes
32
+ end
33
+
34
+ # Test allow and deny same attribute
35
+ def test_deny_overrides_allow
36
+ test_class = Class.new(ActionController::ApplicationParams) do
37
+ allow :admin
38
+ deny :admin
39
+ end
40
+
41
+ # Deny should take precedence
42
+ assert !test_class.allowed?(:admin)
43
+ assert test_class.denied?(:admin)
44
+ end
45
+
46
+ # Test order of allow/deny doesn't matter
47
+ def test_deny_before_allow_still_denies
48
+ test_class = Class.new(ActionController::ApplicationParams) do
49
+ deny :admin
50
+ allow :admin
51
+ end
52
+
53
+ # Deny should still take precedence
54
+ assert !test_class.allowed?(:admin)
55
+ assert test_class.denied?(:admin)
56
+ end
57
+
58
+ # Test empty class
59
+ def test_empty_params_class_has_empty_lists
60
+ test_class = Class.new(ActionController::ApplicationParams)
61
+
62
+ assert_equal [], test_class.allowed_attributes
63
+ assert_equal [], test_class.denied_attributes
64
+ assert_equal({}, test_class.flags)
65
+ assert_equal Set.new, test_class.allowed_metadata
66
+ end
67
+
68
+ # Test flag overwriting
69
+ def test_flag_can_be_overwritten
70
+ test_class = Class.new(ActionController::ApplicationParams) do
71
+ flag :test_flag, true
72
+ flag :test_flag, false
73
+ end
74
+
75
+ assert_equal false, test_class.flag?(:test_flag)
76
+ end
77
+
78
+ # Test flag with different value types
79
+ def test_flag_with_string_value
80
+ test_class = Class.new(ActionController::ApplicationParams) do
81
+ flag :status, 'active'
82
+ end
83
+
84
+ assert_equal 'active', test_class.flag?(:status)
85
+ end
86
+
87
+ def test_flag_with_numeric_value
88
+ test_class = Class.new(ActionController::ApplicationParams) do
89
+ flag :max_count, 100
90
+ end
91
+
92
+ assert_equal 100, test_class.flag?(:max_count)
93
+ end
94
+
95
+ def test_flag_with_array_value
96
+ test_class = Class.new(ActionController::ApplicationParams) do
97
+ flag :allowed_actions, [:create, :update]
98
+ end
99
+
100
+ assert_equal [:create, :update], test_class.flag?(:allowed_actions)
101
+ end
102
+
103
+ def test_flag_with_hash_value
104
+ test_class = Class.new(ActionController::ApplicationParams) do
105
+ flag :config, { min: 1, max: 10 }
106
+ end
107
+
108
+ assert_equal({ min: 1, max: 10 }, test_class.flag?(:config))
109
+ end
110
+
111
+ # Test attribute options with different formats
112
+ def test_allow_with_only_single_action
113
+ test_class = Class.new(ActionController::ApplicationParams) do
114
+ allow :field, only: :create
115
+ end
116
+
117
+ assert_includes test_class.permitted_attributes(action: :create), :field
118
+ refute_includes test_class.permitted_attributes(action: :update), :field
119
+ end
120
+
121
+ def test_allow_with_only_array_of_actions
122
+ test_class = Class.new(ActionController::ApplicationParams) do
123
+ allow :field, only: [:create, :update]
124
+ end
125
+
126
+ assert_includes test_class.permitted_attributes(action: :create), :field
127
+ assert_includes test_class.permitted_attributes(action: :update), :field
128
+ refute_includes test_class.permitted_attributes(action: :destroy), :field
129
+ end
130
+
131
+ def test_allow_with_except_single_action
132
+ test_class = Class.new(ActionController::ApplicationParams) do
133
+ allow :field, except: :destroy
134
+ end
135
+
136
+ assert_includes test_class.permitted_attributes(action: :create), :field
137
+ assert_includes test_class.permitted_attributes(action: :update), :field
138
+ refute_includes test_class.permitted_attributes(action: :destroy), :field
139
+ end
140
+
141
+ def test_allow_with_except_array_of_actions
142
+ test_class = Class.new(ActionController::ApplicationParams) do
143
+ allow :field, except: [:destroy, :delete]
144
+ end
145
+
146
+ assert_includes test_class.permitted_attributes(action: :create), :field
147
+ refute_includes test_class.permitted_attributes(action: :destroy), :field
148
+ refute_includes test_class.permitted_attributes(action: :delete), :field
149
+ end
150
+
151
+ # Test multiple attributes with different options
152
+ def test_multiple_attributes_with_different_action_filters
153
+ test_class = Class.new(ActionController::ApplicationParams) do
154
+ allow :name
155
+ allow :email
156
+ allow :status, only: :create
157
+ allow :updated_by, except: :create
158
+ end
159
+
160
+ create_attrs = test_class.permitted_attributes(action: :create)
161
+ assert_includes create_attrs, :name
162
+ assert_includes create_attrs, :email
163
+ assert_includes create_attrs, :status
164
+ refute_includes create_attrs, :updated_by
165
+
166
+ update_attrs = test_class.permitted_attributes(action: :update)
167
+ assert_includes update_attrs, :name
168
+ assert_includes update_attrs, :email
169
+ refute_includes update_attrs, :status
170
+ assert_includes update_attrs, :updated_by
171
+ end
172
+
173
+ # Test inheritance with complex scenarios
174
+ def test_deep_inheritance_chain
175
+ grandparent = Class.new(ActionController::ApplicationParams) do
176
+ allow :id
177
+ flag :grandparent_flag, true
178
+ end
179
+
180
+ parent = Class.new(grandparent) do
181
+ allow :name
182
+ flag :parent_flag, true
183
+ end
184
+
185
+ child = Class.new(parent) do
186
+ allow :email
187
+ flag :child_flag, true
188
+ end
189
+
190
+ # Child should have all attributes
191
+ assert_includes child.allowed_attributes, :id
192
+ assert_includes child.allowed_attributes, :name
193
+ assert_includes child.allowed_attributes, :email
194
+
195
+ # Child should have all flags
196
+ assert child.flag?(:grandparent_flag)
197
+ assert child.flag?(:parent_flag)
198
+ assert child.flag?(:child_flag)
199
+ end
200
+
201
+ def test_child_modifications_dont_affect_parent
202
+ parent = Class.new(ActionController::ApplicationParams) do
203
+ allow :name
204
+ end
205
+
206
+ child = Class.new(parent) do
207
+ allow :email
208
+ deny :name
209
+ end
210
+
211
+ # Parent should remain unchanged
212
+ assert_includes parent.allowed_attributes, :name
213
+ assert_equal [], parent.denied_attributes
214
+ assert parent.allowed?(:name)
215
+
216
+ # Child should have modifications
217
+ assert_includes child.allowed_attributes, :name
218
+ assert_includes child.allowed_attributes, :email
219
+ assert_includes child.denied_attributes, :name
220
+ assert !child.allowed?(:name)
221
+ end
222
+
223
+ # Test symbol/string conversions
224
+ def test_allow_converts_string_to_symbol
225
+ test_class = Class.new(ActionController::ApplicationParams) do
226
+ allow 'name'
227
+ allow 'email'
228
+ end
229
+
230
+ assert_includes test_class.allowed_attributes, :name
231
+ assert_includes test_class.allowed_attributes, :email
232
+ end
233
+
234
+ def test_deny_converts_string_to_symbol
235
+ test_class = Class.new(ActionController::ApplicationParams) do
236
+ deny 'admin'
237
+ end
238
+
239
+ assert_includes test_class.denied_attributes, :admin
240
+ end
241
+
242
+ def test_flag_converts_string_name_to_symbol
243
+ test_class = Class.new(ActionController::ApplicationParams) do
244
+ flag 'test_flag', true
245
+ end
246
+
247
+ assert test_class.flag?(:test_flag)
248
+ assert test_class.flag?('test_flag')
249
+ end
250
+
251
+ # Test permitted_attributes without action filter
252
+ def test_permitted_attributes_without_action_includes_all
253
+ test_class = Class.new(ActionController::ApplicationParams) do
254
+ allow :name
255
+ allow :status, only: :create
256
+ allow :email, except: :destroy
257
+ end
258
+
259
+ attrs = test_class.permitted_attributes
260
+ assert_includes attrs, :name
261
+ assert_includes attrs, :status
262
+ assert_includes attrs, :email
263
+ end
264
+
265
+ # Test metadata edge cases
266
+ def test_metadata_with_duplicate_keys
267
+ test_class = Class.new(ActionController::ApplicationParams) do
268
+ metadata :ip_address
269
+ metadata :ip_address
270
+ metadata :ip_address
271
+ end
272
+
273
+ # Should only be stored once
274
+ assert_equal 1, test_class.allowed_metadata.size
275
+ assert test_class.metadata_allowed?(:ip_address)
276
+ end
277
+
278
+ def test_metadata_with_mixed_symbol_string
279
+ test_class = Class.new(ActionController::ApplicationParams) do
280
+ metadata :key1
281
+ metadata 'key2'
282
+ end
283
+
284
+ assert test_class.metadata_allowed?(:key1)
285
+ assert test_class.metadata_allowed?('key1')
286
+ assert test_class.metadata_allowed?(:key2)
287
+ assert test_class.metadata_allowed?('key2')
288
+ end
289
+
290
+ # Test attribute_options with non-existent attribute
291
+ def test_attribute_options_for_non_existent_attribute
292
+ test_class = Class.new(ActionController::ApplicationParams) do
293
+ allow :name
294
+ end
295
+
296
+ opts = test_class.attribute_options(:non_existent)
297
+ assert_equal({}, opts)
298
+ end
299
+
300
+ # Test attribute_options overwriting
301
+ def test_allow_same_attribute_twice_with_different_options
302
+ test_class = Class.new(ActionController::ApplicationParams) do
303
+ allow :field, only: :create
304
+ allow :field, except: :destroy
305
+ end
306
+
307
+ # Second call should overwrite options
308
+ opts = test_class.attribute_options(:field)
309
+ assert_nil opts[:only]
310
+ assert_equal [:destroy], opts[:except]
311
+ end
312
+
313
+ # Test empty action filters
314
+ def test_allow_with_empty_only_array
315
+ test_class = Class.new(ActionController::ApplicationParams) do
316
+ allow :field, only: []
317
+ end
318
+
319
+ # Empty array should exclude field from all actions
320
+ refute_includes test_class.permitted_attributes(action: :create), :field
321
+ refute_includes test_class.permitted_attributes(action: :update), :field
322
+ end
323
+
324
+ def test_allow_with_empty_except_array
325
+ test_class = Class.new(ActionController::ApplicationParams) do
326
+ allow :field, except: []
327
+ end
328
+
329
+ # Empty array should include field in all actions
330
+ assert_includes test_class.permitted_attributes(action: :create), :field
331
+ assert_includes test_class.permitted_attributes(action: :update), :field
332
+ end
333
+
334
+ # Test complex inheritance with metadata
335
+ def test_inheritance_with_metadata_additions
336
+ parent = Class.new(ActionController::ApplicationParams) do
337
+ metadata :parent_meta
338
+ end
339
+
340
+ child = Class.new(parent) do
341
+ metadata :child_meta
342
+ end
343
+
344
+ assert child.metadata_allowed?(:parent_meta)
345
+ assert child.metadata_allowed?(:child_meta)
346
+ assert parent.metadata_allowed?(:parent_meta)
347
+ assert !parent.metadata_allowed?(:child_meta)
348
+ end
349
+
350
+ # Test inheritance with attribute_options
351
+ def test_inheritance_copies_attribute_options
352
+ parent = Class.new(ActionController::ApplicationParams) do
353
+ allow :field, only: :create
354
+ end
355
+
356
+ child = Class.new(parent)
357
+
358
+ opts = child.attribute_options(:field)
359
+ assert_equal [:create], opts[:only]
360
+ end
361
+ end