modular_strong_params 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 546b2c752e9f8c6461488c0a7b4046a1ce26c1bd
4
+ data.tar.gz: 7ca4d52d67cc2e8ac3ac21e8af4c08deb40a6d09
5
+ SHA512:
6
+ metadata.gz: 5cb36cef03b5ef5d72d7425606823b11b3c2472dcd977338f4f80c322bd6c128fc0c5f828e144d8fe122e9d640edaaf7ae26eeedee2ced94c4e8d48ca2a277ec
7
+ data.tar.gz: 33fb8906f751a4c9b54ffd60a832df86102a136bd6e0ea21fdab64e77d658a6547be35e2d8977b737f1529dd9c730753c5e822da392d7d1dd0358bbd72b4506b
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1 @@
1
+ ruby-2.2.2
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in modular_strong_params.gemspec
4
+ gemspec
@@ -0,0 +1,56 @@
1
+ # ModularStrongParams
2
+
3
+ This is `StrongParameters` from Ruby on Rails, just... without ActionController. It was literally copied from [the source](https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/strong_parameters.rb) and reorganized so it can be used as module.
4
+
5
+ Error classes are now namespaced with `StrongParameters::Error`. Other explicit references to ActionController have been removed. Please get in touch if anything lingers.
6
+
7
+ ## Installation
8
+
9
+ In your gemfile:
10
+
11
+ ```
12
+ gem 'modular_strong_params', '~> 1.0.0'
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ `lib/action_controller.rb` more or less demonstrates its use.
18
+
19
+ ```ruby
20
+
21
+ require 'strong_parameters'
22
+
23
+ class MyAppClass
24
+ include StrongParameters
25
+
26
+ def params
27
+ @_params ||= Parameters.new(method_that_returns_base_params_hash)
28
+ end
29
+
30
+ def params=(value)
31
+ @_params = value.is_a?(Hash) ? Parameters.new(value) : value
32
+ end
33
+ end
34
+ ```
35
+
36
+ Those methods and the `method_that_returns_base_params_hash` will need to be adapted to your environment. For instance, with a Sinatra app at work, we do this:
37
+
38
+ ```
39
+ def params
40
+ @params ||= StrongParameters::Parameters.new(super)
41
+ end
42
+ ```
43
+
44
+ This gets Sinatra's magical params first, then turns them into a StrongParams. Though you take a bit of a performance hit here, we think that the rewards outweigh the costs.
45
+
46
+ ## Credit Where Credit's Due
47
+
48
+ Full credit for this code goes to those who contributed to this code in Rails.
49
+
50
+ ### Maintenance
51
+
52
+ Since this code has been copied from Rails, it will need to be maintained separately. We cannot claim to keep up on that constantly. If you come across changes to the source that are not reflected here, please submit a PR with passing tests.
53
+
54
+ ## License
55
+
56
+ Released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,25 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ test_files = Dir.glob('test/**/*_test.rb')
5
+
6
+ desc 'Default Task'
7
+ task default: :test
8
+
9
+ # Run the unit tests
10
+ Rake::TestTask.new do |t|
11
+ t.libs << 'test'
12
+ t.test_files = test_files
13
+
14
+ t.warning = true
15
+ t.verbose = true
16
+ t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION)
17
+ end
18
+
19
+ namespace :test do
20
+ task :isolated do
21
+ test_files.all? do |file|
22
+ sh(Gem.ruby, '-w', '-Ilib:test', file)
23
+ end || fail('Failures')
24
+ end
25
+ end
@@ -0,0 +1,125 @@
1
+ require 'abstract_unit'
2
+ require 'strong_parameters'
3
+ require 'active_support/core_ext/hash/transform_values'
4
+
5
+ class ParametersAccessorsTest < ActiveSupport::TestCase
6
+ setup do
7
+ @params = ActionController::Parameters.new(
8
+ person: {
9
+ age: '32',
10
+ name: {
11
+ first: 'David',
12
+ last: 'Heinemeier Hansson'
13
+ },
14
+ addresses: [{city: 'Chicago', state: 'Illinois'}]
15
+ }
16
+ )
17
+ end
18
+
19
+ test '[] retains permitted status' do
20
+ @params.permit!
21
+ assert @params[:person].permitted?
22
+ assert @params[:person][:name].permitted?
23
+ end
24
+
25
+ test '[] retains unpermitted status' do
26
+ assert_not @params[:person].permitted?
27
+ assert_not @params[:person][:name].permitted?
28
+ end
29
+
30
+ test 'each carries permitted status' do
31
+ @params.permit!
32
+ @params.each { |key, value| assert(value.permitted?) if key == 'person' }
33
+ end
34
+
35
+ test 'each carries unpermitted status' do
36
+ @params.each { |key, value| assert_not(value.permitted?) if key == 'person' }
37
+ end
38
+
39
+ test 'each_pair carries permitted status' do
40
+ @params.permit!
41
+ @params.each_pair { |key, value| assert(value.permitted?) if key == 'person' }
42
+ end
43
+
44
+ test 'each_pair carries unpermitted status' do
45
+ @params.each_pair { |key, value| assert_not(value.permitted?) if key == 'person' }
46
+ end
47
+
48
+ test 'except retains permitted status' do
49
+ @params.permit!
50
+ assert @params.except(:person).permitted?
51
+ assert @params[:person].except(:name).permitted?
52
+ end
53
+
54
+ test 'except retains unpermitted status' do
55
+ assert_not @params.except(:person).permitted?
56
+ assert_not @params[:person].except(:name).permitted?
57
+ end
58
+
59
+ test 'fetch retains permitted status' do
60
+ @params.permit!
61
+ assert @params.fetch(:person).permitted?
62
+ assert @params[:person].fetch(:name).permitted?
63
+ end
64
+
65
+ test 'fetch retains unpermitted status' do
66
+ assert_not @params.fetch(:person).permitted?
67
+ assert_not @params[:person].fetch(:name).permitted?
68
+ end
69
+
70
+ test 'reject retains permitted status' do
71
+ assert_not @params.reject { |k| k == 'person' }.permitted?
72
+ end
73
+
74
+ test 'reject retains unpermitted status' do
75
+ @params.permit!
76
+ assert @params.reject { |k| k == 'person' }.permitted?
77
+ end
78
+
79
+ test 'select retains permitted status' do
80
+ @params.permit!
81
+ assert @params.select { |k| k == 'person' }.permitted?
82
+ end
83
+
84
+ test 'select retains unpermitted status' do
85
+ assert_not @params.select { |k| k == 'person' }.permitted?
86
+ end
87
+
88
+ test 'slice retains permitted status' do
89
+ @params.permit!
90
+ assert @params.slice(:person).permitted?
91
+ end
92
+
93
+ test 'slice retains unpermitted status' do
94
+ assert_not @params.slice(:person).permitted?
95
+ end
96
+
97
+ test 'transform_keys retains permitted status' do
98
+ @params.permit!
99
+ assert @params.transform_keys { |k| k }.permitted?
100
+ end
101
+
102
+ test 'transform_keys retains unpermitted status' do
103
+ assert_not @params.transform_keys { |k| k }.permitted?
104
+ end
105
+
106
+ test 'transform_values retains permitted status' do
107
+ @params.permit!
108
+ assert @params.transform_values { |v| v }.permitted?
109
+ end
110
+
111
+ test 'transform_values retains unpermitted status' do
112
+ assert_not @params.transform_values { |v| v }.permitted?
113
+ end
114
+
115
+ test 'values_at retains permitted status' do
116
+ @params.permit!
117
+ assert @params.values_at(:person).first.permitted?
118
+ assert @params[:person].values_at(:name).first.permitted?
119
+ end
120
+
121
+ test 'values_at retains unpermitted status' do
122
+ assert_not @params.values_at(:person).first.permitted?
123
+ assert_not @params[:person].values_at(:name).first.permitted?
124
+ end
125
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'strong_parameters'
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require 'irb'
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,21 @@
1
+ module ActionController
2
+ include StrongParameters
3
+
4
+ module StrongParameters
5
+ extend ActiveSupport::Concern
6
+ include ActiveSupport::Rescuable
7
+
8
+ # Returns a new ActionController::Parameters object that
9
+ # has been instantiated with the <tt>request.parameters</tt>.
10
+ def params
11
+ @_params ||= Parameters.new(request.parameters)
12
+ end
13
+
14
+ # Assigns the given +value+ to the +params+ hash. If +value+
15
+ # is a Hash, this will create an ActionController::Parameters
16
+ # object that has been instantiated with the given +value+ hash.
17
+ def params=(value)
18
+ @_params = value.is_a?(Hash) ? Parameters.new(value) : value
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module ModularStrongParams
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,558 @@
1
+ require 'modular_strong_params/version'
2
+ require 'strong_parameters/error'
3
+ require 'active_support/core_ext/array/wrap'
4
+ require 'active_support/core_ext/string/filters'
5
+ require 'active_support/rescuable'
6
+ require 'active_support/core_ext/hash/indifferent_access'
7
+ require 'action_dispatch/http/upload'
8
+ require 'rack/test'
9
+ require 'stringio'
10
+ require 'set'
11
+
12
+ module StrongParameters
13
+ include StrongParameters::Error
14
+
15
+ # == Strong \Parameters
16
+ #
17
+ # It provides an interface for protecting attributes from end-user
18
+ # assignment. This makes Action Controller parameters forbidden
19
+ # to be used in Active Model mass assignment until they have been
20
+ # whitelisted.
21
+ #
22
+ # In addition, parameters can be marked as required and flow through a
23
+ # predefined raise/rescue flow to end up as a 400 Bad Request with no
24
+ # effort.
25
+ #
26
+ # class PeopleController < ActionController::Base
27
+ # # Using "Person.create(params[:person])" would raise an
28
+ # # ActiveModel::ForbiddenAttributes exception because it'd
29
+ # # be using mass assignment without an explicit permit step.
30
+ # # This is the recommended form:
31
+ # def create
32
+ # Person.create(person_params)
33
+ # end
34
+ #
35
+ # # This will pass with flying colors as long as there's a person key in the
36
+ # # parameters, otherwise it'll raise an ActionController::MissingParameter
37
+ # # exception, which will get caught by ActionController::Base and turned
38
+ # # into a 400 Bad Request reply.
39
+ # def update
40
+ # redirect_to current_account.people.find(params[:id]).tap { |person|
41
+ # person.update!(person_params)
42
+ # }
43
+ # end
44
+ #
45
+ # private
46
+ # # Using a private method to encapsulate the permissible parameters is
47
+ # # just a good pattern since you'll be able to reuse the same permit
48
+ # # list between create and update. Also, you can specialize this method
49
+ # # with per-user checking of permissible attributes.
50
+ # def person_params
51
+ # params.require(:person).permit(:name, :age)
52
+ # end
53
+ # end
54
+ #
55
+ # In order to use <tt>accepts_nested_attributes_for</tt> with Strong \Parameters, you
56
+ # will need to specify which nested attributes should be whitelisted.
57
+ #
58
+ # class Person
59
+ # has_many :pets
60
+ # accepts_nested_attributes_for :pets
61
+ # end
62
+ #
63
+ # class PeopleController < ActionController::Base
64
+ # def create
65
+ # Person.create(person_params)
66
+ # end
67
+ #
68
+ # ...
69
+ #
70
+ # private
71
+ #
72
+ # def person_params
73
+ # # It's mandatory to specify the nested attributes that should be whitelisted.
74
+ # # If you use `permit` with just the key that points to the nested attributes hash,
75
+ # # it will return an empty hash.
76
+ # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
77
+ # end
78
+ # end
79
+ #
80
+ # See ActionController::Parameters.require and ActionController::Parameters.permit
81
+ # for more information.V
82
+ class Parameters < ActiveSupport::HashWithIndifferentAccess
83
+ cattr_accessor :permit_all_parameters, instance_accessor: false
84
+ cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
85
+
86
+ # By default, never raise an UnpermittedParameters exception if these
87
+ # params are present. The default includes both 'controller' and 'action'
88
+ # because they are added by Rails and should be of no concern. One way
89
+ # to change these is to specify `always_permitted_parameters` in your
90
+ # config. For instance:
91
+ #
92
+ # config.always_permitted_parameters = %w( controller action format )
93
+ cattr_accessor :always_permitted_parameters
94
+ self.always_permitted_parameters = %w( controller action )
95
+
96
+ def self.const_missing(const_name)
97
+ return super unless const_name == :NEVER_UNPERMITTED_PARAMS
98
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
99
+ `ActionController::Parameters::NEVER_UNPERMITTED_PARAMS` has been deprecated.
100
+ Use `ActionController::Parameters.always_permitted_parameters` instead.
101
+ MSG
102
+
103
+ always_permitted_parameters
104
+ end
105
+
106
+ # Returns a new instance of <tt>ActionController::Parameters</tt>.
107
+ # Also, sets the +permitted+ attribute to the default value of
108
+ # <tt>ActionController::Parameters.permit_all_parameters</tt>.
109
+ #
110
+ # class Person < ActiveRecord::Base
111
+ # end
112
+ #
113
+ # params = ActionController::Parameters.new(name: 'Francesco')
114
+ # params.permitted? # => false
115
+ # Person.new(params) # => ActiveModel::ForbiddenAttributesError
116
+ #
117
+ # ActionController::Parameters.permit_all_parameters = true
118
+ #
119
+ # params = ActionController::Parameters.new(name: 'Francesco')
120
+ # params.permitted? # => true
121
+ # Person.new(params) # => #<Person id: nil, name: "Francesco">
122
+ def initialize(attributes = nil)
123
+ super(attributes)
124
+ @permitted = self.class.permit_all_parameters
125
+ end
126
+
127
+ # Returns a safe +Hash+ representation of this parameter with all
128
+ # unpermitted keys removed.
129
+ #
130
+ # params = ActionController::Parameters.new({
131
+ # name: 'Senjougahara Hitagi',
132
+ # oddity: 'Heavy stone crab'
133
+ # })
134
+ # params.to_h # => {}
135
+ #
136
+ # safe_params = params.permit(:name)
137
+ # safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
138
+ def to_h
139
+ if permitted?
140
+ to_hash
141
+ else
142
+ slice(*self.class.always_permitted_parameters).permit!.to_h
143
+ end
144
+ end
145
+
146
+ # Returns an unsafe, unfiltered +Hash+ representation of this parameter.
147
+ def to_unsafe_h
148
+ to_hash
149
+ end
150
+ alias_method :to_unsafe_hash, :to_unsafe_h
151
+
152
+ # Convert all hashes in values into parameters, then yield each pair like
153
+ # the same way as <tt>Hash#each_pair</tt>
154
+ def each_pair(&block)
155
+ super do |key, value|
156
+ convert_hashes_to_parameters(key, value)
157
+ end
158
+
159
+ super
160
+ end
161
+
162
+ alias_method :each, :each_pair
163
+
164
+ # Attribute that keeps track of converted arrays, if any, to avoid double
165
+ # looping in the common use case permit + mass-assignment. Defined in a
166
+ # method to instantiate it only if needed.
167
+ #
168
+ # Testing membership still loops, but it's going to be faster than our own
169
+ # loop that converts values. Also, we are not going to build a new array
170
+ # object per fetch.
171
+ def converted_arrays
172
+ @converted_arrays ||= Set.new
173
+ end
174
+
175
+ # Returns +true+ if the parameter is permitted, +false+ otherwise.
176
+ #
177
+ # params = ActionController::Parameters.new
178
+ # params.permitted? # => false
179
+ # params.permit!
180
+ # params.permitted? # => true
181
+ def permitted?
182
+ @permitted
183
+ end
184
+
185
+ # Sets the +permitted+ attribute to +true+. This can be used to pass
186
+ # mass assignment. Returns +self+.
187
+ #
188
+ # class Person < ActiveRecord::Base
189
+ # end
190
+ #
191
+ # params = ActionController::Parameters.new(name: 'Francesco')
192
+ # params.permitted? # => false
193
+ # Person.new(params) # => ActiveModel::ForbiddenAttributesError
194
+ # params.permit!
195
+ # params.permitted? # => true
196
+ # Person.new(params) # => #<Person id: nil, name: "Francesco">
197
+ def permit!
198
+ each_pair do |_key, value|
199
+ Array.wrap(value).each do |v|
200
+ v.permit! if v.respond_to? :permit!
201
+ end
202
+ end
203
+
204
+ @permitted = true
205
+ self
206
+ end
207
+
208
+ # Ensures that a parameter is present. If it's present, returns
209
+ # the parameter at the given +key+, otherwise raises an
210
+ # <tt>ActionController::ParameterMissing</tt> error.
211
+ #
212
+ # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
213
+ # # => {"name"=>"Francesco"}
214
+ #
215
+ # ActionController::Parameters.new(person: nil).require(:person)
216
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: person
217
+ #
218
+ # ActionController::Parameters.new(person: {}).require(:person)
219
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: person
220
+ def require(key)
221
+ value = self[key]
222
+ if value.present? || value == false
223
+ value
224
+ else
225
+ fail ParameterMissing.new(key)
226
+ end
227
+ end
228
+
229
+ # Alias of #require.
230
+ alias_method :required, :require
231
+
232
+ # Returns a new <tt>ActionController::Parameters</tt> instance that
233
+ # includes only the given +filters+ and sets the +permitted+ attribute
234
+ # for the object to +true+. This is useful for limiting which attributes
235
+ # should be allowed for mass updating.
236
+ #
237
+ # params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
238
+ # permitted = params.require(:user).permit(:name, :age)
239
+ # permitted.permitted? # => true
240
+ # permitted.has_key?(:name) # => true
241
+ # permitted.has_key?(:age) # => true
242
+ # permitted.has_key?(:role) # => false
243
+ #
244
+ # Only permitted scalars pass the filter. For example, given
245
+ #
246
+ # params.permit(:name)
247
+ #
248
+ # +:name+ passes if it is a key of +params+ whose associated value is of type
249
+ # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
250
+ # +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
251
+ # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
252
+ # Otherwise, the key +:name+ is filtered out.
253
+ #
254
+ # You may declare that the parameter should be an array of permitted scalars
255
+ # by mapping it to an empty array:
256
+ #
257
+ # params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
258
+ # params.permit(tags: [])
259
+ #
260
+ # You can also use +permit+ on nested parameters, like:
261
+ #
262
+ # params = ActionController::Parameters.new({
263
+ # person: {
264
+ # name: 'Francesco',
265
+ # age: 22,
266
+ # pets: [{
267
+ # name: 'Purplish',
268
+ # category: 'dogs'
269
+ # }]
270
+ # }
271
+ # })
272
+ #
273
+ # permitted = params.permit(person: [ :name, { pets: :name } ])
274
+ # permitted.permitted? # => true
275
+ # permitted[:person][:name] # => "Francesco"
276
+ # permitted[:person][:age] # => nil
277
+ # permitted[:person][:pets][0][:name] # => "Purplish"
278
+ # permitted[:person][:pets][0][:category] # => nil
279
+ #
280
+ # Note that if you use +permit+ in a key that points to a hash,
281
+ # it won't allow all the hash. You also need to specify which
282
+ # attributes inside the hash should be whitelisted.
283
+ #
284
+ # params = ActionController::Parameters.new({
285
+ # person: {
286
+ # contact: {
287
+ # email: 'none@test.com',
288
+ # phone: '555-1234'
289
+ # }
290
+ # }
291
+ # })
292
+ #
293
+ # params.require(:person).permit(:contact)
294
+ # # => {}
295
+ #
296
+ # params.require(:person).permit(contact: :phone)
297
+ # # => {"contact"=>{"phone"=>"555-1234"}}
298
+ #
299
+ # params.require(:person).permit(contact: [ :email, :phone ])
300
+ # # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
301
+ def permit(*filters)
302
+ params = self.class.new
303
+
304
+ filters.flatten.each do |filter|
305
+ case filter
306
+ when Symbol, String
307
+ permitted_scalar_filter(params, filter)
308
+ when Hash then
309
+ hash_filter(params, filter)
310
+ end
311
+ end
312
+
313
+ unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
314
+
315
+ params.permit!
316
+ end
317
+
318
+ # Returns a parameter for the given +key+. If not found,
319
+ # returns +nil+.
320
+ #
321
+ # params = ActionController::Parameters.new(person: { name: 'Francesco' })
322
+ # params[:person] # => {"name"=>"Francesco"}
323
+ # params[:none] # => nil
324
+ def [](key)
325
+ convert_hashes_to_parameters(key, super)
326
+ end
327
+
328
+ # Returns a parameter for the given +key+. If the +key+
329
+ # can't be found, there are several options: With no other arguments,
330
+ # it will raise an <tt>ActionController::ParameterMissing</tt> error;
331
+ # if more arguments are given, then that will be returned; if a block
332
+ # is given, then that will be run and its result returned.
333
+ #
334
+ # params = ActionController::Parameters.new(person: { name: 'Francesco' })
335
+ # params.fetch(:person) # => {"name"=>"Francesco"}
336
+ # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
337
+ # params.fetch(:none, 'Francesco') # => "Francesco"
338
+ # params.fetch(:none) { 'Francesco' } # => "Francesco"
339
+ def fetch(key, *args)
340
+ convert_hashes_to_parameters(key, super, false)
341
+ rescue KeyError
342
+ raise StrongParameters::Error::ParameterMissing.new(key)
343
+ end
344
+
345
+ # Returns a new <tt>ActionController::Parameters</tt> instance that
346
+ # includes only the given +keys+. If the given +keys+
347
+ # don't exist, returns an empty hash.
348
+ #
349
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
350
+ # params.slice(:a, :b) # => {"a"=>1, "b"=>2}
351
+ # params.slice(:d) # => {}
352
+ def slice(*keys)
353
+ new_instance_with_inherited_permitted_status(super)
354
+ end
355
+
356
+ # Removes and returns the key/value pairs matching the given keys.
357
+ #
358
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
359
+ # params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
360
+ # params # => {"c"=>3}
361
+ def extract!(*keys)
362
+ new_instance_with_inherited_permitted_status(super)
363
+ end
364
+
365
+ # Returns a new <tt>ActionController::Parameters</tt> with the results of
366
+ # running +block+ once for every value. The keys are unchanged.
367
+ #
368
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
369
+ # params.transform_values { |x| x * 2 }
370
+ # # => {"a"=>2, "b"=>4, "c"=>6}
371
+ def transform_values
372
+ if block_given?
373
+ new_instance_with_inherited_permitted_status(super)
374
+ else
375
+ super
376
+ end
377
+ end
378
+
379
+ # This method is here only to make sure that the returned object has the
380
+ # correct +permitted+ status. It should not matter since the parent of
381
+ # this object is +HashWithIndifferentAccess+
382
+ def transform_keys # :nodoc:
383
+ if block_given?
384
+ new_instance_with_inherited_permitted_status(super)
385
+ else
386
+ super
387
+ end
388
+ end
389
+
390
+ # Deletes and returns a key-value pair from +Parameters+ whose key is equal
391
+ # to key. If the key is not found, returns the default value. If the
392
+ # optional code block is given and the key is not found, pass in the key
393
+ # and return the result of block.
394
+ def delete(key, &block)
395
+ convert_hashes_to_parameters(key, super, false)
396
+ end
397
+
398
+ # Equivalent to Hash#keep_if, but returns nil if no changes were made.
399
+ def select!(&block)
400
+ convert_value_to_parameters(super)
401
+ end
402
+
403
+ # Returns an exact copy of the <tt>ActionController::Parameters</tt>
404
+ # instance. +permitted+ state is kept on the duped object.
405
+ #
406
+ # params = ActionController::Parameters.new(a: 1)
407
+ # params.permit!
408
+ # params.permitted? # => true
409
+ # copy_params = params.dup # => {"a"=>1}
410
+ # copy_params.permitted? # => true
411
+ def dup
412
+ super.tap do |duplicate|
413
+ duplicate.permitted = @permitted
414
+ end
415
+ end
416
+
417
+ protected
418
+
419
+ attr_writer :permitted
420
+
421
+ private
422
+
423
+ def new_instance_with_inherited_permitted_status(hash)
424
+ self.class.new(hash).tap do |new_instance|
425
+ new_instance.permitted = @permitted
426
+ end
427
+ end
428
+
429
+ def convert_hashes_to_parameters(key, value, assign_if_converted = true)
430
+ converted = convert_value_to_parameters(value)
431
+ self[key] = converted if assign_if_converted && !converted.equal?(value)
432
+ converted
433
+ end
434
+
435
+ def convert_value_to_parameters(value)
436
+ if value.is_a?(Array) && !converted_arrays.member?(value)
437
+ converted = value.map { |_| convert_value_to_parameters(_) }
438
+ converted_arrays << converted
439
+ converted
440
+ elsif value.is_a?(Parameters) || !value.is_a?(Hash)
441
+ value
442
+ else
443
+ self.class.new(value)
444
+ end
445
+ end
446
+
447
+ def each_element(object)
448
+ if object.is_a?(Array)
449
+ object.map { |el| yield el }.compact
450
+ elsif fields_for_style?(object)
451
+ hash = object.class.new
452
+ object.each { |k, v| hash[k] = yield v }
453
+ hash
454
+ else
455
+ yield object
456
+ end
457
+ end
458
+
459
+ def fields_for_style?(object)
460
+ object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
461
+ end
462
+
463
+ def unpermitted_parameters!(params)
464
+ unpermitted_keys = unpermitted_keys(params)
465
+ if unpermitted_keys.any?
466
+ case self.class.action_on_unpermitted_parameters
467
+ when :log
468
+ name = 'unpermitted_parameters.action_controller'
469
+ ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
470
+ when :raise
471
+ fail StrongParameters::Error::UnpermittedParameters.new(unpermitted_keys)
472
+ end
473
+ end
474
+ end
475
+
476
+ def unpermitted_keys(params)
477
+ self.keys - params.keys - self.always_permitted_parameters
478
+ end
479
+
480
+ #
481
+ # --- Filtering ----------------------------------------------------------
482
+ #
483
+
484
+ # This is a white list of permitted scalar types that includes the ones
485
+ # supported in XML and JSON requests.
486
+ #
487
+ # This list is in particular used to filter ordinary requests, String goes
488
+ # as first element to quickly short-circuit the common case.
489
+ #
490
+ # If you modify this collection please update the API of +permit+ above.
491
+ PERMITTED_SCALAR_TYPES = [
492
+ String,
493
+ Symbol,
494
+ NilClass,
495
+ Numeric,
496
+ TrueClass,
497
+ FalseClass,
498
+ Date,
499
+ Time,
500
+ # DateTimes are Dates, we document the type but avoid the redundant check.
501
+ StringIO,
502
+ IO,
503
+ ActionDispatch::Http::UploadedFile,
504
+ ::Rack::Test::UploadedFile
505
+ ]
506
+
507
+ def permitted_scalar?(value)
508
+ PERMITTED_SCALAR_TYPES.any? { |type| value.is_a?(type) }
509
+ end
510
+
511
+ def permitted_scalar_filter(params, key)
512
+ if key?(key) && permitted_scalar?(self[key])
513
+ params[key] = self[key]
514
+ end
515
+
516
+ keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
517
+ if permitted_scalar?(self[k])
518
+ params[k] = self[k]
519
+ end
520
+ end
521
+ end
522
+
523
+ def array_of_permitted_scalars?(value)
524
+ if value.is_a?(Array)
525
+ value.all? { |element| permitted_scalar?(element) }
526
+ end
527
+ end
528
+
529
+ def array_of_permitted_scalars_filter(params, key)
530
+ if key?(key) && array_of_permitted_scalars?(self[key])
531
+ params[key] = self[key]
532
+ end
533
+ end
534
+
535
+ EMPTY_ARRAY = []
536
+ def hash_filter(params, filter)
537
+ filter = filter.with_indifferent_access
538
+
539
+ # Slicing filters out non-declared keys.
540
+ slice(*filter.keys).each do |key, value|
541
+ next unless value
542
+
543
+ if filter[key] == EMPTY_ARRAY
544
+ # Declaration { comment_ids: [] }.
545
+ array_of_permitted_scalars_filter(params, key)
546
+ else
547
+ # Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
548
+ params[key] = each_element(value) do |element|
549
+ if element.is_a?(Hash)
550
+ element = self.class.new(element) unless element.respond_to?(:permit)
551
+ element.permit(*Array.wrap(filter[key]))
552
+ end
553
+ end
554
+ end
555
+ end
556
+ end
557
+ end
558
+ end
@@ -0,0 +1,35 @@
1
+ module StrongParameters
2
+ module Error
3
+ # Raised when a required parameter is missing.
4
+ #
5
+ # params = ActionController::Parameters.new(a: {})
6
+ # params.fetch(:b)
7
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: b
8
+ # params.require(:a)
9
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: a
10
+ class ParameterMissing < KeyError
11
+ attr_reader :param # :nodoc:
12
+
13
+ def initialize(param) # :nodoc:
14
+ @param = param
15
+ super("param is missing or the value is empty: #{param}")
16
+ end
17
+ end
18
+
19
+ # Raised when a supplied parameter is not expected and
20
+ # ActionController::Parameters.action_on_unpermitted_parameters
21
+ # is set to <tt>:raise</tt>.
22
+ #
23
+ # params = ActionController::Parameters.new(a: "123", b: "456")
24
+ # params.permit(:c)
25
+ # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b
26
+ class UnpermittedParameters < IndexError
27
+ attr_reader :params # :nodoc:
28
+
29
+ def initialize(params) # :nodoc:
30
+ @params = params
31
+ super("found unpermitted parameter#{'s' if params.size > 1 }: #{params.join(', ')}")
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'modular_strong_params/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'modular_strong_params'
8
+ spec.version = ModularStrongParams::VERSION
9
+ spec.authors = ['Chris Grigg', 'Rebecca Eakins']
10
+ spec.email = ['chris@subvertallmedia.com', 'rebecca.a.eakins@gmail.com']
11
+
12
+ spec.summary = 'Strong params... in a module!'
13
+ spec.description = %q{Rails 4.2\'s Strong Parameters in modular form, usable in Sinatra.
14
+ The listed authors are only responsible for code extraction, full credit and respect
15
+ goes to those talented individuals who actually wrote the code!}
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = 'exe'
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ['lib']
21
+ spec.license = 'MIT'
22
+
23
+ spec.add_dependency 'rack'
24
+ spec.add_dependency 'activesupport', '~> 4.2'
25
+ spec.add_dependency 'actionpack', '~> 4.2'
26
+ spec.add_dependency 'activemodel', '~> 4.2'
27
+ spec.add_dependency 'railties', '~> 4.2'
28
+
29
+ spec.add_development_dependency 'bundler', '~> 1.9'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ spec.add_development_dependency 'test-unit'
32
+ spec.add_development_dependency 'pry'
33
+ end
metadata ADDED
@@ -0,0 +1,190 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: modular_strong_params
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Grigg
8
+ - Rebecca Eakins
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2015-07-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4.2'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '4.2'
42
+ - !ruby/object:Gem::Dependency
43
+ name: actionpack
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '4.2'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '4.2'
56
+ - !ruby/object:Gem::Dependency
57
+ name: activemodel
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '4.2'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '4.2'
70
+ - !ruby/object:Gem::Dependency
71
+ name: railties
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '4.2'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '4.2'
84
+ - !ruby/object:Gem::Dependency
85
+ name: bundler
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '1.9'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '1.9'
98
+ - !ruby/object:Gem::Dependency
99
+ name: rake
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '10.0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '10.0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: test-unit
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: pry
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ description: |-
141
+ Rails 4.2\'s Strong Parameters in modular form, usable in Sinatra.
142
+ The listed authors are only responsible for code extraction, full credit and respect
143
+ goes to those talented individuals who actually wrote the code!
144
+ email:
145
+ - chris@subvertallmedia.com
146
+ - rebecca.a.eakins@gmail.com
147
+ executables: []
148
+ extensions: []
149
+ extra_rdoc_files: []
150
+ files:
151
+ - ".gitignore"
152
+ - ".rspec"
153
+ - ".ruby-version"
154
+ - ".travis.yml"
155
+ - Gemfile
156
+ - README.md
157
+ - Rakefile
158
+ - accessors_test.rb
159
+ - bin/console
160
+ - bin/setup
161
+ - lib/action_controller.rb
162
+ - lib/modular_strong_params/version.rb
163
+ - lib/strong_parameters.rb
164
+ - lib/strong_parameters/error.rb
165
+ - modular_strong_params.gemspec
166
+ homepage:
167
+ licenses:
168
+ - MIT
169
+ metadata: {}
170
+ post_install_message:
171
+ rdoc_options: []
172
+ require_paths:
173
+ - lib
174
+ required_ruby_version: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ required_rubygems_version: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - ">="
182
+ - !ruby/object:Gem::Version
183
+ version: '0'
184
+ requirements: []
185
+ rubyforge_project:
186
+ rubygems_version: 2.4.6
187
+ signing_key:
188
+ specification_version: 4
189
+ summary: Strong params... in a module!
190
+ test_files: []