stronger_parameters 1.0.0
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.
- checksums.yaml +7 -0
- data/README.md +157 -0
- data/lib/stronger_parameters.rb +6 -0
- data/lib/stronger_parameters/constraints.rb +84 -0
- data/lib/stronger_parameters/constraints/array_constraint.rb +25 -0
- data/lib/stronger_parameters/constraints/boolean_constraint.rb +20 -0
- data/lib/stronger_parameters/constraints/comparison_constraints.rb +47 -0
- data/lib/stronger_parameters/constraints/enumeration_constraint.rb +21 -0
- data/lib/stronger_parameters/constraints/fixnum_constraint.rb +15 -0
- data/lib/stronger_parameters/constraints/hash_constraint.rb +28 -0
- data/lib/stronger_parameters/constraints/string_constraint.rb +27 -0
- data/lib/stronger_parameters/errors.rb +19 -0
- data/lib/stronger_parameters/parameters.rb +136 -0
- data/lib/stronger_parameters/railtie.rb +11 -0
- data/lib/stronger_parameters/version.rb +3 -0
- data/test/array_contraints_test.rb +12 -0
- data/test/boolean_constraints_test.rb +17 -0
- data/test/comparison_constraints_test.rb +35 -0
- data/test/controller_test.rb +26 -0
- data/test/enum_constraints_test.rb +12 -0
- data/test/fixnum_constraints_test.rb +13 -0
- data/test/hash_constraint_test.rb +28 -0
- data/test/operator_constraints_test.rb +22 -0
- data/test/string_constraints_test.rb +15 -0
- data/test/test_helper.rb +72 -0
- metadata +190 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7f1ac00b775682d4c9c6afb626ba93e34c5592d5
|
4
|
+
data.tar.gz: a91d5b4d4d1ca33e232b71761cfce8698f70e989
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ddb1bc2266c76e9796903d0f5610878ab6435ae0a99191bebb94e462ecd710deb5b3c4ab3e5b6d5ee4c1101d124015102c9900eb21eceddd71e49a8f18c8b76a
|
7
|
+
data.tar.gz: f64f92a712ab52389c838315cc71dc6d9b3f292adfd9a8638213a6d84187762f5c2dd372e838c98a08dd38c7bd3564d45a19b9a98d5961138485bd341b481b48
|
data/README.md
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# stronger_parameters
|
2
|
+
|
3
|
+
This is an extension of `strong_parameters` with added type checking.
|
4
|
+
|
5
|
+
## Simple types
|
6
|
+
You can specify simple types like this:
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
params.permit(
|
10
|
+
:id => Parameters.id,
|
11
|
+
:name => Parameters.string
|
12
|
+
)
|
13
|
+
```
|
14
|
+
|
15
|
+
## Arrays
|
16
|
+
You can specify arrays like this:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
params.permit(
|
20
|
+
:id => Parameters.array(Parameters.id)
|
21
|
+
)
|
22
|
+
```
|
23
|
+
|
24
|
+
This will allow an array of id parameters that all are IDs.
|
25
|
+
|
26
|
+
## Nested Parameters
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
params.permit(
|
30
|
+
:name => Parameters.string,
|
31
|
+
:emails => Parameters.array(Parameters.string),
|
32
|
+
:friends => Parameters.array(
|
33
|
+
Parameters.map(
|
34
|
+
:name => Parameters.string,
|
35
|
+
:family => Parameters.map(
|
36
|
+
:name => Parameters.string
|
37
|
+
)
|
38
|
+
:hobbies => Parameters.array(Parameters.string)
|
39
|
+
)
|
40
|
+
)
|
41
|
+
)
|
42
|
+
```
|
43
|
+
|
44
|
+
This will allow parameters like this:
|
45
|
+
|
46
|
+
```json
|
47
|
+
{
|
48
|
+
"name": "Mick",
|
49
|
+
"emails": ["mick@zendesk.com", "mick@staugaard.com"],
|
50
|
+
"friends": [
|
51
|
+
{"name": "Morten", "family": {"name": "Primdahl"}, "hobbies": ["work", "art"]},
|
52
|
+
{"name": "Eric", "family": {"name": "Chapweske"}, "hobbies": ["boating", "whiskey"]}
|
53
|
+
]
|
54
|
+
}
|
55
|
+
```
|
56
|
+
|
57
|
+
### ActiveModel Nested Attributes
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
params.require(:author).permit(
|
61
|
+
:name => Parameters.string,
|
62
|
+
:books_attributes => Parameters.array(
|
63
|
+
Parameters.map(
|
64
|
+
:title => Parameters.string,
|
65
|
+
:id => Parameters.id,
|
66
|
+
:_destroy => Parameters.boolean
|
67
|
+
)
|
68
|
+
)
|
69
|
+
)
|
70
|
+
```
|
71
|
+
|
72
|
+
This will allow parameters like this:
|
73
|
+
|
74
|
+
```json
|
75
|
+
{
|
76
|
+
"author": {
|
77
|
+
"name": "Eric Chapweske",
|
78
|
+
"books_attributes": [
|
79
|
+
{"title": "Boatin' and Drinkin'", "id": 234, "_destroy": true},
|
80
|
+
{"title": "Advanced Boatin' and Drinkin'", "id": 567}
|
81
|
+
]
|
82
|
+
}
|
83
|
+
}
|
84
|
+
```
|
85
|
+
|
86
|
+
## Combining Requirements
|
87
|
+
|
88
|
+
If you want to permit a parameter to be one of multiple types, you can use the `|` operator:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
params.require(:ticket).permit(
|
92
|
+
:status => Parameters.id | Parameters.enum('open', 'closed')
|
93
|
+
)
|
94
|
+
```
|
95
|
+
|
96
|
+
This will allow these parameter sets:
|
97
|
+
|
98
|
+
```json
|
99
|
+
{
|
100
|
+
"ticket": {
|
101
|
+
"status": 123
|
102
|
+
}
|
103
|
+
}
|
104
|
+
```
|
105
|
+
```json
|
106
|
+
{
|
107
|
+
"ticket": {
|
108
|
+
"status": "open"
|
109
|
+
}
|
110
|
+
}
|
111
|
+
```
|
112
|
+
|
113
|
+
You can use the `&` operator to apply further restrictions on the type:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
params.require(:user).permit(
|
117
|
+
:age => Parameters.integer & Parameters.gte(0)
|
118
|
+
)
|
119
|
+
```
|
120
|
+
|
121
|
+
This requires the parameter to be an integer greater than or equal to 0.
|
122
|
+
|
123
|
+
### Combining Requirements in Arrays
|
124
|
+
|
125
|
+
You can also use the `|` and `&` operators in arrays:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
params.require(:group).permit(
|
129
|
+
:users => Parameters.array(Parameters.id | Parameters.string)
|
130
|
+
)
|
131
|
+
```
|
132
|
+
|
133
|
+
This will permit these parameters:
|
134
|
+
```json
|
135
|
+
{
|
136
|
+
"group": {
|
137
|
+
"users": [123, "mick@zendesk.com", 345, 676, "morten@zendesk.com"]
|
138
|
+
}
|
139
|
+
}
|
140
|
+
```
|
141
|
+
|
142
|
+
## Types
|
143
|
+
|
144
|
+
| Syntax | (Simplified) Definition |
|
145
|
+
|--------------------------------|-------------------------------------------------------------------------|
|
146
|
+
| Parameters.string | value.is_a?(String) |
|
147
|
+
| Parameters.integer | value.is_a?(Fixnum) |
|
148
|
+
| Parameters.enum('asc', 'desc') | ['asc', 'desc'].include?(value) |
|
149
|
+
| Parameters.lt(10) | value < 10 |
|
150
|
+
| Parameters.lte(10) | value <= 10 |
|
151
|
+
| Parameters.gt(0) | value > 0 |
|
152
|
+
| Parameters.gte(0) | value >= 0 |
|
153
|
+
| Parameters.integer32 | Parameters.integer & Parameters.lte(2 ** 31) & Parameters.gte(-2 ** 31) |
|
154
|
+
| Parameters.integer64 | Parameters.integer & Parameters.lte(2 ** 63) & Parameters.gte(-2 ** 63) |
|
155
|
+
| Parameters.id | Parameters.integer & Parameters.lte(2 ** 31) & Parameters.gte(0) |
|
156
|
+
| Parameters.bigid | Parameters.integer & Parameters.lte(2 ** 63) & Parameters.gte(0) |
|
157
|
+
| Parameters.boolean | Parameters.enum(true, false, 'true', 'false', 1, 0) |
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'stronger_parameters/errors'
|
2
|
+
|
3
|
+
module StrongerParameters
|
4
|
+
class Constraint
|
5
|
+
def value(v)
|
6
|
+
v
|
7
|
+
end
|
8
|
+
|
9
|
+
def |(other)
|
10
|
+
OrConstraint.new(self, other)
|
11
|
+
end
|
12
|
+
|
13
|
+
def &(other)
|
14
|
+
AndConstraint.new(self, other)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
self.class == other.class
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class OrConstraint < Constraint
|
23
|
+
attr_reader :constraints
|
24
|
+
|
25
|
+
def initialize(*constraints)
|
26
|
+
@constraints = constraints
|
27
|
+
end
|
28
|
+
|
29
|
+
def value(v)
|
30
|
+
exception = nil
|
31
|
+
|
32
|
+
constraints.each do |c|
|
33
|
+
begin
|
34
|
+
return c.value(v)
|
35
|
+
rescue InvalidParameter => e
|
36
|
+
exception ||= e
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
raise exception
|
41
|
+
end
|
42
|
+
|
43
|
+
def |(other)
|
44
|
+
constraints << other
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def ==(other)
|
49
|
+
super && constraints == other.constraints
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class AndConstraint < Constraint
|
54
|
+
attr_reader :constraints
|
55
|
+
|
56
|
+
def initialize(*constraints)
|
57
|
+
@constraints = constraints
|
58
|
+
end
|
59
|
+
|
60
|
+
def value(v)
|
61
|
+
constraints.each do |c|
|
62
|
+
v = c.value(v)
|
63
|
+
end
|
64
|
+
v
|
65
|
+
end
|
66
|
+
|
67
|
+
def &(other)
|
68
|
+
constraints << other
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def ==(other)
|
73
|
+
super && constraints == other.constraints
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
require 'stronger_parameters/constraints/string_constraint'
|
79
|
+
require 'stronger_parameters/constraints/fixnum_constraint'
|
80
|
+
require 'stronger_parameters/constraints/boolean_constraint'
|
81
|
+
require 'stronger_parameters/constraints/array_constraint'
|
82
|
+
require 'stronger_parameters/constraints/hash_constraint'
|
83
|
+
require 'stronger_parameters/constraints/enumeration_constraint'
|
84
|
+
require 'stronger_parameters/constraints/comparison_constraints'
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'stronger_parameters/constraints'
|
2
|
+
|
3
|
+
module StrongerParameters
|
4
|
+
class ArrayConstraint < Constraint
|
5
|
+
attr_reader :item_constraint
|
6
|
+
|
7
|
+
def initialize(item_constraint)
|
8
|
+
@item_constraint = item_constraint
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(v)
|
12
|
+
if v.is_a?(Array)
|
13
|
+
return v.map do |item|
|
14
|
+
item_constraint.value(item)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
raise InvalidParameter.new(v, "must be an array")
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(other)
|
22
|
+
super && item_constraint == other.item_constraint
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'stronger_parameters/constraints'
|
2
|
+
|
3
|
+
module StrongerParameters
|
4
|
+
class BooleanConstraint < Constraint
|
5
|
+
TRUE_VALUES = [true, 'true', '1', 1].freeze
|
6
|
+
FALSE_VALUES = [false, 'false', '0', 0].freeze
|
7
|
+
|
8
|
+
def value(v)
|
9
|
+
if TRUE_VALUES.include?(v)
|
10
|
+
return true
|
11
|
+
end
|
12
|
+
|
13
|
+
if FALSE_VALUES.include?(v)
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
|
17
|
+
raise InvalidParameter.new(v, "must be either true or false")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'stronger_parameters/constraints'
|
2
|
+
|
3
|
+
module StrongerParameters
|
4
|
+
class ComparisonConstraints < Constraint
|
5
|
+
attr_reader :limit
|
6
|
+
|
7
|
+
def initialize(limit)
|
8
|
+
@limit = limit
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
super && limit == other.limit
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class LessThanConstraint < ComparisonConstraints
|
17
|
+
def value(v)
|
18
|
+
return v if v < limit
|
19
|
+
|
20
|
+
raise InvalidParameter.new(v, "must be less than #{limit}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class LessThanOrEqualConstraint < ComparisonConstraints
|
25
|
+
def value(v)
|
26
|
+
return v if v <= limit
|
27
|
+
|
28
|
+
raise InvalidParameter.new(v, "must be less than or equal to #{limit}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class GreaterThanConstraint < ComparisonConstraints
|
33
|
+
def value(v)
|
34
|
+
return v if v > limit
|
35
|
+
|
36
|
+
raise InvalidParameter.new(v, "must be greater than #{limit}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class GreaterThanOrEqualConstraint < ComparisonConstraints
|
41
|
+
def value(v)
|
42
|
+
return v if v >= limit
|
43
|
+
|
44
|
+
raise InvalidParameter.new(v, "must be greater than or equal to #{limit}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'stronger_parameters/constraints'
|
2
|
+
|
3
|
+
module StrongerParameters
|
4
|
+
class EnumerationConstraint < Constraint
|
5
|
+
attr_reader :allowed
|
6
|
+
|
7
|
+
def initialize(*allowed)
|
8
|
+
@allowed = allowed
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(v)
|
12
|
+
return v if allowed.include?(v)
|
13
|
+
|
14
|
+
raise InvalidParameter.new(v, "must be one of these: #{allowed.to_sentence}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
super && allowed == other.allowed
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'stronger_parameters/constraints'
|
2
|
+
|
3
|
+
module StrongerParameters
|
4
|
+
class FixnumConstraint < Constraint
|
5
|
+
def value(v)
|
6
|
+
if v.is_a?(Fixnum)
|
7
|
+
return v
|
8
|
+
elsif v.is_a?(String) && v =~ /^\d+$/
|
9
|
+
return v.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
raise InvalidParameter.new(v, 'must be an integer')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'stronger_parameters/constraints'
|
2
|
+
|
3
|
+
module StrongerParameters
|
4
|
+
class HashConstraint < Constraint
|
5
|
+
attr_reader :constraints
|
6
|
+
|
7
|
+
def initialize(constraints)
|
8
|
+
@constraints = constraints.with_indifferent_access
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(v)
|
12
|
+
if v.is_a?(Hash)
|
13
|
+
return ActionController::Parameters.new(v).permit(constraints)
|
14
|
+
end
|
15
|
+
|
16
|
+
raise InvalidParameter.new(v, "must be a hash")
|
17
|
+
end
|
18
|
+
|
19
|
+
def merge(other)
|
20
|
+
other_constraints = other.is_a?(HashConstraint) ? other.constraints : other
|
21
|
+
self.class.new(constraints.merge(other_constraints))
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
super && constraints == other.constraints
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'stronger_parameters/constraints'
|
2
|
+
|
3
|
+
module StrongerParameters
|
4
|
+
class StringConstraint < Constraint
|
5
|
+
attr_reader :maximum_length
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@maximum_length = options[:maximum_length] || options[:max_length]
|
9
|
+
end
|
10
|
+
|
11
|
+
def value(v)
|
12
|
+
if v.is_a?(String)
|
13
|
+
if maximum_length && v.bytesize > maximum_length
|
14
|
+
raise InvalidParameter.new(v, "can not be longer than #{maximum_length} bytes")
|
15
|
+
end
|
16
|
+
|
17
|
+
return v
|
18
|
+
end
|
19
|
+
|
20
|
+
raise InvalidParameter.new(v, 'must be a string')
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
super && maximum_length == other.maximum_length
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module StrongerParameters
|
2
|
+
class InvalidParameter < StandardError
|
3
|
+
attr_accessor :key, :value, :message
|
4
|
+
|
5
|
+
def initialize(value, message)
|
6
|
+
@value = value
|
7
|
+
@message = message
|
8
|
+
super(message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
if key.present?
|
13
|
+
"found invalid value for #{key}. Value #{super}"
|
14
|
+
else
|
15
|
+
"found invalid value. Value #{super}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'action_pack'
|
2
|
+
|
3
|
+
if ActionPack::VERSION::MAJOR == 3
|
4
|
+
require 'action_controller/parameters'
|
5
|
+
else
|
6
|
+
require 'action_controller/metal/strong_parameters'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'stronger_parameters/constraints'
|
10
|
+
require 'stronger_parameters/errors'
|
11
|
+
|
12
|
+
module StrongerParameters
|
13
|
+
module Parameters
|
14
|
+
extend ActiveSupport::Concern
|
15
|
+
|
16
|
+
included do
|
17
|
+
alias_method_chain :hash_filter, :stronger_parameters
|
18
|
+
cattr_accessor :action_on_invalid_parameters, :instance_accessor => false
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
def anything
|
23
|
+
Constraint.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def string(options = {})
|
27
|
+
StringConstraint.new(options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def integer
|
31
|
+
@integer ||= FixnumConstraint.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def lt(limit)
|
35
|
+
LessThanConstraint.new(limit)
|
36
|
+
end
|
37
|
+
|
38
|
+
def lte(limit)
|
39
|
+
LessThanOrEqualConstraint.new(limit)
|
40
|
+
end
|
41
|
+
|
42
|
+
def gt(limit)
|
43
|
+
GreaterThanConstraint.new(limit)
|
44
|
+
end
|
45
|
+
|
46
|
+
def gte(limit)
|
47
|
+
GreaterThanOrEqualConstraint.new(limit)
|
48
|
+
end
|
49
|
+
|
50
|
+
def integer32
|
51
|
+
integer & lte(2 ** 31) & gte(-2 ** 31)
|
52
|
+
end
|
53
|
+
|
54
|
+
def integer64
|
55
|
+
integer & lte(2 ** 63) & gte(-2 ** 63)
|
56
|
+
end
|
57
|
+
|
58
|
+
def id
|
59
|
+
integer & lte(2 ** 31) & gte(0)
|
60
|
+
end
|
61
|
+
|
62
|
+
def bigid
|
63
|
+
integer & lte(2 ** 63) & gte(0)
|
64
|
+
end
|
65
|
+
|
66
|
+
def enumeration(*allowed)
|
67
|
+
EnumerationConstraint.new(*allowed)
|
68
|
+
end
|
69
|
+
alias_method :enum, :enumeration
|
70
|
+
|
71
|
+
def boolean
|
72
|
+
BooleanConstraint.new
|
73
|
+
end
|
74
|
+
|
75
|
+
def array(item_constraint)
|
76
|
+
ArrayConstraint.new(item_constraint)
|
77
|
+
end
|
78
|
+
|
79
|
+
def map(constraints)
|
80
|
+
HashConstraint.new(constraints)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def hash_filter_with_stronger_parameters(params, filter)
|
85
|
+
stronger_filter = ActiveSupport::HashWithIndifferentAccess.new
|
86
|
+
other_filter = ActiveSupport::HashWithIndifferentAccess.new
|
87
|
+
|
88
|
+
filter.each do |k,v|
|
89
|
+
if v.is_a?(Constraint)
|
90
|
+
stronger_filter[k] = v
|
91
|
+
else
|
92
|
+
other_filter[k] = v
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
hash_filter_without_stronger_parameters(params, other_filter)
|
97
|
+
|
98
|
+
slice(*stronger_filter.keys).each do |key, value|
|
99
|
+
if value.nil?
|
100
|
+
params[key] = nil
|
101
|
+
next
|
102
|
+
end
|
103
|
+
|
104
|
+
constraint = stronger_filter[key]
|
105
|
+
begin
|
106
|
+
params[key] = constraint.value(value)
|
107
|
+
rescue InvalidParameter => e
|
108
|
+
e.key = key
|
109
|
+
|
110
|
+
name = "invalid_parameter.action_controller"
|
111
|
+
ActiveSupport::Notifications.publish(name, :key => key, :value => value, :message => e.message)
|
112
|
+
|
113
|
+
params[key] = value
|
114
|
+
|
115
|
+
raise unless self.class.action_on_invalid_parameters == :log
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
module ControllerSupport
|
123
|
+
extend ActiveSupport::Concern
|
124
|
+
|
125
|
+
Parameters = ActionController::Parameters
|
126
|
+
|
127
|
+
included do
|
128
|
+
rescue_from(StrongerParameters::InvalidParameter) do |e|
|
129
|
+
render :text => "Invalid parameter: #{e.key} #{e.message}", :status => :bad_request
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
ActionController::Parameters.send :include, StrongerParameters::Parameters
|
136
|
+
ActionController::Base.send :include, StrongerParameters::ControllerSupport
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rails/railtie'
|
2
|
+
|
3
|
+
module StrongParameters
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
initializer "stronger_parameters.config", :before => "action_controller.set_configs" do |app|
|
6
|
+
ActionController::Parameters.action_on_invalid_parameters = app.config.action_controller.delete(:action_on_invalid_parameters) do
|
7
|
+
(Rails.env.test? || Rails.env.development?) ? :log : false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe 'array parameter constraints' do
|
4
|
+
subject { ActionController::Parameters.array(ActionController::Parameters.string) }
|
5
|
+
|
6
|
+
permits ['a', 'b']
|
7
|
+
|
8
|
+
rejects 'abc'
|
9
|
+
rejects 123
|
10
|
+
rejects [123, 456]
|
11
|
+
rejects ['abc', 123]
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe 'boolean parameter constraints' do
|
4
|
+
subject { ActionController::Parameters.boolean }
|
5
|
+
|
6
|
+
permits true, :as => true
|
7
|
+
permits 'true', :as => true
|
8
|
+
permits 1, :as => true
|
9
|
+
permits '1', :as => true
|
10
|
+
|
11
|
+
permits false, :as => false
|
12
|
+
permits 'false', :as => false
|
13
|
+
permits 0, :as => false
|
14
|
+
permits '0', :as => false
|
15
|
+
|
16
|
+
rejects 'foo'
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe 'comparison parameter constraints' do
|
4
|
+
describe 'less-than types' do
|
5
|
+
subject { ActionController::Parameters.lt(2) }
|
6
|
+
|
7
|
+
permits 1
|
8
|
+
rejects 2
|
9
|
+
rejects 3
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'less-than-or-equal types' do
|
13
|
+
subject { ActionController::Parameters.lte(2) }
|
14
|
+
|
15
|
+
permits 1
|
16
|
+
permits 2
|
17
|
+
rejects 3
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'greater-than types' do
|
21
|
+
subject { ActionController::Parameters.gt(2) }
|
22
|
+
|
23
|
+
rejects 1
|
24
|
+
rejects 2
|
25
|
+
permits 3
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'greater-than-or-equal types' do
|
29
|
+
subject { ActionController::Parameters.gte(2) }
|
30
|
+
|
31
|
+
rejects 1
|
32
|
+
permits 2
|
33
|
+
permits 3
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class BooksController < ActionController::Base
|
4
|
+
def create
|
5
|
+
params.require(:book).permit(:id => Parameters.integer)
|
6
|
+
|
7
|
+
head :ok
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe BooksController do
|
12
|
+
it 'rejects invalid params' do
|
13
|
+
post :create, { :magazine => { :name => 'Mjallo!' } }
|
14
|
+
assert_response :bad_request
|
15
|
+
response.body.must_equal 'Required parameter missing: book'
|
16
|
+
|
17
|
+
post :create, { :book => { :id => 'Mjallo!' } }
|
18
|
+
assert_response :bad_request
|
19
|
+
response.body.must_equal 'Invalid parameter: id must be an integer'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'permits valid params' do
|
23
|
+
post :create, { :book => { :id => '123' } }
|
24
|
+
assert_response :ok
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe 'array parameter constraints' do
|
4
|
+
subject do
|
5
|
+
ActionController::Parameters.map(
|
6
|
+
:id => ActionController::Parameters.integer,
|
7
|
+
:name => ActionController::Parameters.string
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.permits(value, options = {})
|
12
|
+
options[:as] ||= value
|
13
|
+
options[:as] = options[:as].with_indifferent_access
|
14
|
+
|
15
|
+
super(value, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
permits(:id => 1, :name => 'Mick')
|
19
|
+
permits({:id => '1', :name => 'Mick'}, :as => {:id => 1, :name => 'Mick'})
|
20
|
+
permits(:id => 1)
|
21
|
+
permits({:id => '1'}, :as => {:id => 1})
|
22
|
+
permits(:name => 'Mick')
|
23
|
+
|
24
|
+
rejects(:id => 1, :name => 123)
|
25
|
+
rejects(:id => 'Mick', :name => 'Mick')
|
26
|
+
rejects(123)
|
27
|
+
rejects('abc')
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe 'operator parameter constraints' do
|
4
|
+
describe 'OR types' do
|
5
|
+
subject { ActionController::Parameters.integer | ActionController::Parameters.string }
|
6
|
+
|
7
|
+
permits 'abc'
|
8
|
+
permits '123', :as => 123
|
9
|
+
|
10
|
+
rejects Date.today
|
11
|
+
rejects Time.now
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'AND types' do
|
15
|
+
subject { ActionController::Parameters.string & ActionController::Parameters.integer }
|
16
|
+
|
17
|
+
permits '123', :as => 123
|
18
|
+
|
19
|
+
rejects 123
|
20
|
+
rejects 'abc'
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
describe 'string parameter constraints' do
|
4
|
+
subject { ActionController::Parameters.string }
|
5
|
+
|
6
|
+
permits 'abc'
|
7
|
+
|
8
|
+
rejects 123
|
9
|
+
rejects Date.today
|
10
|
+
rejects Time.now
|
11
|
+
|
12
|
+
it 'rejects strings that are too long' do
|
13
|
+
assert_rejects(:value) { params(:value => '123').permit(:value => ActionController::Parameters.string(:max_length => 2)) }
|
14
|
+
end
|
15
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
ENV["RAILS_ENV"] = "test"
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'rails'
|
5
|
+
require 'action_controller'
|
6
|
+
require 'rails/test_help'
|
7
|
+
|
8
|
+
class FakeApplication < Rails::Application; end
|
9
|
+
|
10
|
+
Rails.application = FakeApplication
|
11
|
+
Rails.configuration.action_controller = ActiveSupport::OrderedOptions.new
|
12
|
+
Rails.configuration.secret_key_base = 'secret_key_base'
|
13
|
+
|
14
|
+
require 'action_pack'
|
15
|
+
require 'strong_parameters' if ActionPack::VERSION::MAJOR == 3
|
16
|
+
|
17
|
+
module ActionController
|
18
|
+
SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
|
19
|
+
SharedTestRoutes.draw do
|
20
|
+
get ':controller(/:action)'
|
21
|
+
post ':controller(/:action)'
|
22
|
+
put ':controller(/:action)'
|
23
|
+
delete ':controller(/:action)'
|
24
|
+
end
|
25
|
+
|
26
|
+
class Base
|
27
|
+
include ActionController::Testing
|
28
|
+
include SharedTestRoutes.url_helpers
|
29
|
+
|
30
|
+
rescue_from(ActionController::ParameterMissing) do |e|
|
31
|
+
render :text => "Required parameter missing: #{e.param}", :status => :bad_request
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ActionController::TestCase
|
36
|
+
setup do
|
37
|
+
@routes = SharedTestRoutes
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
require 'stronger_parameters'
|
43
|
+
require 'minitest/rails'
|
44
|
+
require 'minitest/autorun'
|
45
|
+
|
46
|
+
class MiniTest::Spec
|
47
|
+
def params(hash)
|
48
|
+
ActionController::Parameters.new(hash)
|
49
|
+
end
|
50
|
+
|
51
|
+
def assert_rejects(key, &block)
|
52
|
+
err = block.must_raise StrongerParameters::InvalidParameter
|
53
|
+
err.key.must_equal key.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.permits(value, options = {})
|
57
|
+
type_casted = options.fetch(:as, value)
|
58
|
+
|
59
|
+
it "permits #{value.inspect} as #{type_casted.inspect}" do
|
60
|
+
permitted = params(:value => value).permit(:value => subject)
|
61
|
+
permitted[:value].must_equal type_casted
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.rejects(value, options = {})
|
66
|
+
key = options.fetch(:key, :value)
|
67
|
+
|
68
|
+
it "rejects #{value.inspect}" do
|
69
|
+
assert_rejects(key) { params(:value => value).permit(:value => subject) }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,190 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stronger_parameters
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mick Staugaard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: guard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest-rails
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: appraisal
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: actionpack
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>'
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '2'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>'
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '2'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
- mick@zendesk.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- lib/stronger_parameters/constraints/array_constraint.rb
|
133
|
+
- lib/stronger_parameters/constraints/boolean_constraint.rb
|
134
|
+
- lib/stronger_parameters/constraints/comparison_constraints.rb
|
135
|
+
- lib/stronger_parameters/constraints/enumeration_constraint.rb
|
136
|
+
- lib/stronger_parameters/constraints/fixnum_constraint.rb
|
137
|
+
- lib/stronger_parameters/constraints/hash_constraint.rb
|
138
|
+
- lib/stronger_parameters/constraints/string_constraint.rb
|
139
|
+
- lib/stronger_parameters/constraints.rb
|
140
|
+
- lib/stronger_parameters/errors.rb
|
141
|
+
- lib/stronger_parameters/parameters.rb
|
142
|
+
- lib/stronger_parameters/railtie.rb
|
143
|
+
- lib/stronger_parameters/version.rb
|
144
|
+
- lib/stronger_parameters.rb
|
145
|
+
- test/array_contraints_test.rb
|
146
|
+
- test/boolean_constraints_test.rb
|
147
|
+
- test/comparison_constraints_test.rb
|
148
|
+
- test/controller_test.rb
|
149
|
+
- test/enum_constraints_test.rb
|
150
|
+
- test/fixnum_constraints_test.rb
|
151
|
+
- test/hash_constraint_test.rb
|
152
|
+
- test/operator_constraints_test.rb
|
153
|
+
- test/string_constraints_test.rb
|
154
|
+
- test/test_helper.rb
|
155
|
+
- README.md
|
156
|
+
homepage: https://github.com/zendesk/stronger_parameters
|
157
|
+
licenses:
|
158
|
+
- Apache License Version 2.0
|
159
|
+
metadata: {}
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
requirements: []
|
175
|
+
rubyforge_project:
|
176
|
+
rubygems_version: 2.0.14
|
177
|
+
signing_key:
|
178
|
+
specification_version: 4
|
179
|
+
summary: Type checking and type casting of parameters for Action Pack
|
180
|
+
test_files:
|
181
|
+
- test/array_contraints_test.rb
|
182
|
+
- test/boolean_constraints_test.rb
|
183
|
+
- test/comparison_constraints_test.rb
|
184
|
+
- test/controller_test.rb
|
185
|
+
- test/enum_constraints_test.rb
|
186
|
+
- test/fixnum_constraints_test.rb
|
187
|
+
- test/hash_constraint_test.rb
|
188
|
+
- test/operator_constraints_test.rb
|
189
|
+
- test/string_constraints_test.rb
|
190
|
+
- test/test_helper.rb
|