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,422 @@
1
+ require 'test_helper'
2
+ require 'thread'
3
+
4
+ class ParamsRegistryConcurrencyTest < Minitest::Test
5
+ def setup
6
+ ActionController::ParamsRegistry.clear!
7
+ end
8
+
9
+ def teardown
10
+ ActionController::ParamsRegistry.clear!
11
+ end
12
+
13
+ # Test concurrent registrations
14
+ def test_concurrent_registrations
15
+ threads = []
16
+ params_classes = {}
17
+
18
+ # Create multiple params classes
19
+ 10.times do |i|
20
+ params_classes["Model#{i}"] = Class.new(ActionController::ApplicationParams) do
21
+ define_singleton_method(:name) { "Model#{i}Params" }
22
+ allow :field
23
+ end
24
+ end
25
+
26
+ # Register them concurrently
27
+ 10.times do |i|
28
+ threads << Thread.new do
29
+ ActionController::ParamsRegistry.register("Model#{i}", params_classes["Model#{i}"])
30
+ end
31
+ end
32
+
33
+ threads.each(&:join)
34
+
35
+ # Verify all were registered
36
+ 10.times do |i|
37
+ assert ActionController::ParamsRegistry.registered?("Model#{i}")
38
+ assert_equal params_classes["Model#{i}"], ActionController::ParamsRegistry.lookup("Model#{i}")
39
+ end
40
+ end
41
+
42
+ # Test concurrent lookups
43
+ def test_concurrent_lookups
44
+ params_class = Class.new(ActionController::ApplicationParams) do
45
+ def self.name
46
+ 'TestParams'
47
+ end
48
+ allow :field
49
+ end
50
+
51
+ ActionController::ParamsRegistry.register('Test', params_class)
52
+
53
+ threads = []
54
+ results = []
55
+ mutex = Mutex.new
56
+
57
+ # Perform concurrent lookups
58
+ 20.times do
59
+ threads << Thread.new do
60
+ result = ActionController::ParamsRegistry.lookup('Test')
61
+ mutex.synchronize do
62
+ results << result
63
+ end
64
+ end
65
+ end
66
+
67
+ threads.each(&:join)
68
+
69
+ # All should have found the same class
70
+ assert_equal 20, results.length
71
+ results.each do |result|
72
+ assert_equal params_class, result
73
+ end
74
+ end
75
+
76
+ # Test concurrent registered? checks
77
+ def test_concurrent_registered_checks
78
+ params_class = Class.new(ActionController::ApplicationParams) do
79
+ def self.name
80
+ 'TestParams'
81
+ end
82
+ allow :field
83
+ end
84
+
85
+ ActionController::ParamsRegistry.register('Test', params_class)
86
+
87
+ threads = []
88
+ results = []
89
+ mutex = Mutex.new
90
+
91
+ # Perform concurrent checks
92
+ 20.times do
93
+ threads << Thread.new do
94
+ result = ActionController::ParamsRegistry.registered?('Test')
95
+ mutex.synchronize do
96
+ results << result
97
+ end
98
+ end
99
+ end
100
+
101
+ threads.each(&:join)
102
+
103
+ # All should return true
104
+ assert_equal 20, results.length
105
+ assert results.all? { |r| r == true }
106
+ end
107
+
108
+ # Test concurrent permitted_attributes_for
109
+ def test_concurrent_permitted_attributes_for
110
+ params_class = Class.new(ActionController::ApplicationParams) do
111
+ def self.name
112
+ 'TestParams'
113
+ end
114
+ allow :field1
115
+ allow :field2
116
+ allow :field3
117
+ end
118
+
119
+ ActionController::ParamsRegistry.register('Test', params_class)
120
+
121
+ threads = []
122
+ results = []
123
+ mutex = Mutex.new
124
+
125
+ # Perform concurrent attribute lookups
126
+ 20.times do
127
+ threads << Thread.new do
128
+ attrs = ActionController::ParamsRegistry.permitted_attributes_for('Test')
129
+ mutex.synchronize do
130
+ results << attrs
131
+ end
132
+ end
133
+ end
134
+
135
+ threads.each(&:join)
136
+
137
+ # All should return the same attributes
138
+ assert_equal 20, results.length
139
+ expected = [:field1, :field2, :field3]
140
+ results.each do |result|
141
+ assert_equal expected, result
142
+ end
143
+ end
144
+
145
+ # Test concurrent registrations and lookups
146
+ def test_concurrent_registrations_and_lookups
147
+ threads = []
148
+ results = []
149
+ mutex = Mutex.new
150
+
151
+ params_classes = {}
152
+ 5.times do |i|
153
+ params_classes["Model#{i}"] = Class.new(ActionController::ApplicationParams) do
154
+ define_singleton_method(:name) { "Model#{i}Params" }
155
+ allow "field#{i}".to_sym
156
+ end
157
+ end
158
+
159
+ # Mix registrations and lookups
160
+ 10.times do |i|
161
+ if i < 5
162
+ # First 5 threads register
163
+ threads << Thread.new do
164
+ ActionController::ParamsRegistry.register("Model#{i}", params_classes["Model#{i}"])
165
+ end
166
+ else
167
+ # Next 5 threads lookup
168
+ threads << Thread.new do
169
+ sleep 0.01 # Small delay to allow some registrations
170
+ idx = i - 5
171
+ result = ActionController::ParamsRegistry.lookup("Model#{idx}")
172
+ mutex.synchronize do
173
+ results << result
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ threads.each(&:join)
180
+
181
+ # All lookups should eventually succeed
182
+ results.each do |result|
183
+ refute_nil result
184
+ end
185
+ end
186
+
187
+ # Test concurrent clear operations
188
+ def test_concurrent_clear_and_register
189
+ threads = []
190
+
191
+ params_class = Class.new(ActionController::ApplicationParams) do
192
+ def self.name
193
+ 'TestParams'
194
+ end
195
+ allow :field
196
+ end
197
+
198
+ # One thread clears, others try to register
199
+ threads << Thread.new do
200
+ 5.times do
201
+ ActionController::ParamsRegistry.clear!
202
+ sleep 0.01
203
+ end
204
+ end
205
+
206
+ 10.times do |i|
207
+ threads << Thread.new do
208
+ 10.times do
209
+ ActionController::ParamsRegistry.register("Model#{i}", params_class)
210
+ sleep 0.005
211
+ end
212
+ end
213
+ end
214
+
215
+ threads.each(&:join)
216
+
217
+ # At the end, some models might be registered depending on timing
218
+ # This test mainly ensures no crashes occur
219
+ models = ActionController::ParamsRegistry.registered_models
220
+ assert models.is_a?(Array)
221
+ end
222
+
223
+ # Test concurrent registered_models calls
224
+ def test_concurrent_registered_models
225
+ 5.times do |i|
226
+ params_class = Class.new(ActionController::ApplicationParams) do
227
+ define_singleton_method(:name) { "Model#{i}Params" }
228
+ allow :field
229
+ end
230
+ ActionController::ParamsRegistry.register("Model#{i}", params_class)
231
+ end
232
+
233
+ threads = []
234
+ results = []
235
+ mutex = Mutex.new
236
+
237
+ 20.times do
238
+ threads << Thread.new do
239
+ models = ActionController::ParamsRegistry.registered_models
240
+ mutex.synchronize do
241
+ results << models.sort
242
+ end
243
+ end
244
+ end
245
+
246
+ threads.each(&:join)
247
+
248
+ # All should return the same models
249
+ expected = (0..4).map { |i| "model#{i}" }.sort
250
+ results.each do |result|
251
+ assert_equal expected, result
252
+ end
253
+ end
254
+
255
+ # Test registry isolation between threads
256
+ def test_registry_shared_state
257
+ params_class1 = Class.new(ActionController::ApplicationParams) do
258
+ def self.name
259
+ 'Params1'
260
+ end
261
+ allow :field1
262
+ end
263
+
264
+ params_class2 = Class.new(ActionController::ApplicationParams) do
265
+ def self.name
266
+ 'Params2'
267
+ end
268
+ allow :field2
269
+ end
270
+
271
+ thread1_result = nil
272
+ thread2_result = nil
273
+
274
+ thread1 = Thread.new do
275
+ ActionController::ParamsRegistry.register('Test1', params_class1)
276
+ sleep 0.02
277
+ thread1_result = ActionController::ParamsRegistry.lookup('Test2')
278
+ end
279
+
280
+ thread2 = Thread.new do
281
+ sleep 0.01
282
+ ActionController::ParamsRegistry.register('Test2', params_class2)
283
+ thread2_result = ActionController::ParamsRegistry.lookup('Test1')
284
+ end
285
+
286
+ thread1.join
287
+ thread2.join
288
+
289
+ # Both threads should see each other's registrations (shared state)
290
+ assert_equal params_class2, thread1_result
291
+ assert_equal params_class1, thread2_result
292
+ end
293
+
294
+ # Test many concurrent operations
295
+ def test_high_concurrency_mixed_operations
296
+ threads = []
297
+ errors = []
298
+ mutex = Mutex.new
299
+
300
+ # Pre-register some classes
301
+ 5.times do |i|
302
+ params_class = Class.new(ActionController::ApplicationParams) do
303
+ define_singleton_method(:name) { "Model#{i}Params" }
304
+ allow :field
305
+ end
306
+ ActionController::ParamsRegistry.register("Model#{i}", params_class)
307
+ end
308
+
309
+ # Perform many mixed operations
310
+ 50.times do |i|
311
+ threads << Thread.new do
312
+ begin
313
+ case i % 4
314
+ when 0
315
+ # Lookup
316
+ ActionController::ParamsRegistry.lookup("Model#{i % 5}")
317
+ when 1
318
+ # Check registered
319
+ ActionController::ParamsRegistry.registered?("Model#{i % 5}")
320
+ when 2
321
+ # Get permitted attributes
322
+ ActionController::ParamsRegistry.permitted_attributes_for("Model#{i % 5}")
323
+ when 3
324
+ # Get registered models
325
+ ActionController::ParamsRegistry.registered_models
326
+ end
327
+ rescue => e
328
+ mutex.synchronize do
329
+ errors << e
330
+ end
331
+ end
332
+ end
333
+ end
334
+
335
+ threads.each(&:join)
336
+
337
+ # Should complete without errors
338
+ assert_equal [], errors, "Errors occurred: #{errors.map(&:message).join(', ')}"
339
+ end
340
+
341
+ # Test transform_params under concurrent access
342
+ def test_concurrent_transform_params
343
+ params_class = Class.new(ActionController::ApplicationParams) do
344
+ def self.name
345
+ 'UserParams'
346
+ end
347
+ allow :name
348
+ allow :email
349
+ end
350
+
351
+ ActionController::ParamsRegistry.register('User', params_class)
352
+
353
+ threads = []
354
+ results = []
355
+ mutex = Mutex.new
356
+
357
+ 20.times do |i|
358
+ threads << Thread.new do
359
+ params = ActionController::Parameters.new(
360
+ user: {
361
+ name: "User#{i}",
362
+ email: "user#{i}@example.com"
363
+ }
364
+ )
365
+
366
+ permitted = params.require(:user).transform_params()
367
+ mutex.synchronize do
368
+ results << permitted.to_h
369
+ end
370
+ end
371
+ end
372
+
373
+ threads.each(&:join)
374
+
375
+ # All should have correct data
376
+ assert_equal 20, results.length
377
+ 20.times do |i|
378
+ matching = results.find { |r| r['name'] == "User#{i}" }
379
+ refute_nil matching, "Missing result for User#{i}"
380
+ assert_equal "user#{i}@example.com", matching['email']
381
+ end
382
+ end
383
+
384
+ # Test ApplicationParams class isolation
385
+ def test_params_class_modifications_thread_safe
386
+ base_class = Class.new(ActionController::ApplicationParams) do
387
+ def self.name
388
+ 'BaseParams'
389
+ end
390
+ allow :id
391
+ end
392
+
393
+ ActionController::ParamsRegistry.register('Base', base_class)
394
+
395
+ threads = []
396
+ results = []
397
+ mutex = Mutex.new
398
+
399
+ # Multiple threads accessing the class attributes
400
+ 20.times do
401
+ threads << Thread.new do
402
+ # Read operations
403
+ attrs = base_class.allowed_attributes.dup
404
+ denied = base_class.denied_attributes.dup
405
+ flags = base_class.flags.dup
406
+
407
+ mutex.synchronize do
408
+ results << { attrs: attrs, denied: denied, flags: flags }
409
+ end
410
+ end
411
+ end
412
+
413
+ threads.each(&:join)
414
+
415
+ # All should see the same state
416
+ results.each do |result|
417
+ assert_equal [:id], result[:attrs]
418
+ assert_equal [], result[:denied]
419
+ assert_equal({}, result[:flags])
420
+ end
421
+ end
422
+ end
@@ -0,0 +1,112 @@
1
+ require 'test_helper'
2
+
3
+ class ParamsRegistryTest < Minitest::Test
4
+ def setup
5
+ ActionController::ParamsRegistry.clear!
6
+
7
+ @user_params_class = Class.new(ActionController::ApplicationParams) do
8
+ def self.name
9
+ 'UserParams'
10
+ end
11
+
12
+ allow :first_name
13
+ allow :last_name
14
+ allow :email
15
+ end
16
+
17
+ @account_params_class = Class.new(ActionController::ApplicationParams) do
18
+ def self.name
19
+ 'AccountParams'
20
+ end
21
+
22
+ allow :name
23
+ allow :description
24
+ end
25
+ end
26
+
27
+ def teardown
28
+ ActionController::ParamsRegistry.clear!
29
+ end
30
+
31
+ def test_register_stores_params_class
32
+ ActionController::ParamsRegistry.register('User', @user_params_class)
33
+ assert_equal @user_params_class, ActionController::ParamsRegistry.lookup('User')
34
+ end
35
+
36
+ def test_register_normalizes_model_name
37
+ ActionController::ParamsRegistry.register('User', @user_params_class)
38
+
39
+ # Should work with different capitalizations and formats
40
+ assert_equal @user_params_class, ActionController::ParamsRegistry.lookup(:user)
41
+ assert_equal @user_params_class, ActionController::ParamsRegistry.lookup('user')
42
+ assert_equal @user_params_class, ActionController::ParamsRegistry.lookup('User')
43
+ end
44
+
45
+ def test_lookup_returns_nil_for_unregistered_model
46
+ assert_nil ActionController::ParamsRegistry.lookup('NonexistentModel')
47
+ end
48
+
49
+ def test_registered_returns_true_for_registered_model
50
+ ActionController::ParamsRegistry.register('User', @user_params_class)
51
+ assert ActionController::ParamsRegistry.registered?('User')
52
+ assert ActionController::ParamsRegistry.registered?(:user)
53
+ end
54
+
55
+ def test_registered_returns_false_for_unregistered_model
56
+ assert !ActionController::ParamsRegistry.registered?('NonexistentModel')
57
+ end
58
+
59
+ def test_permitted_attributes_for_returns_attributes
60
+ ActionController::ParamsRegistry.register('User', @user_params_class)
61
+
62
+ attrs = ActionController::ParamsRegistry.permitted_attributes_for('User')
63
+ assert_equal [:first_name, :last_name, :email], attrs
64
+ end
65
+
66
+ def test_permitted_attributes_for_returns_empty_for_unregistered
67
+ attrs = ActionController::ParamsRegistry.permitted_attributes_for('NonexistentModel')
68
+ assert_equal [], attrs
69
+ end
70
+
71
+ def test_permitted_attributes_for_with_action
72
+ test_class = Class.new(ActionController::ApplicationParams) do
73
+ allow :field1, only: :create
74
+ allow :field2
75
+ end
76
+
77
+ ActionController::ParamsRegistry.register('Test', test_class)
78
+
79
+ # With action: :create, should include both fields
80
+ attrs = ActionController::ParamsRegistry.permitted_attributes_for('Test', action: :create)
81
+ assert_includes attrs, :field1
82
+ assert_includes attrs, :field2
83
+
84
+ # With action: :update, should only include field2
85
+ attrs = ActionController::ParamsRegistry.permitted_attributes_for('Test', action: :update)
86
+ refute_includes attrs, :field1
87
+ assert_includes attrs, :field2
88
+ end
89
+
90
+ def test_clear_removes_all_registrations
91
+ ActionController::ParamsRegistry.register('User', @user_params_class)
92
+ ActionController::ParamsRegistry.register('Account', @account_params_class)
93
+
94
+ assert ActionController::ParamsRegistry.registered?('User')
95
+ assert ActionController::ParamsRegistry.registered?('Account')
96
+
97
+ ActionController::ParamsRegistry.clear!
98
+
99
+ assert !ActionController::ParamsRegistry.registered?('User')
100
+ assert !ActionController::ParamsRegistry.registered?('Account')
101
+ end
102
+
103
+ def test_registered_models_returns_all_models
104
+ ActionController::ParamsRegistry.register('User', @user_params_class)
105
+ ActionController::ParamsRegistry.register('Account', @account_params_class)
106
+
107
+ models = ActionController::ParamsRegistry.registered_models
108
+ assert_equal 2, models.length
109
+ assert_includes models, 'user'
110
+ assert_includes models, 'account'
111
+ end
112
+ end