dry-types 1.1.1 → 1.2.1

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 +4 -4
  2. data/.codeclimate.yml +2 -5
  3. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +10 -0
  4. data/.github/ISSUE_TEMPLATE/---bug-report.md +34 -0
  5. data/.github/ISSUE_TEMPLATE/---feature-request.md +18 -0
  6. data/.github/workflows/custom_ci.yml +76 -0
  7. data/.github/workflows/docsite.yml +34 -0
  8. data/.github/workflows/sync_configs.yml +34 -0
  9. data/.gitignore +1 -1
  10. data/.rspec +3 -1
  11. data/.rubocop.yml +34 -4
  12. data/CHANGELOG.md +69 -0
  13. data/CODE_OF_CONDUCT.md +13 -0
  14. data/CONTRIBUTING.md +2 -2
  15. data/Gemfile +11 -6
  16. data/LICENSE +17 -17
  17. data/docsite/source/array-with-member.html.md +13 -0
  18. data/docsite/source/built-in-types.html.md +116 -0
  19. data/docsite/source/constraints.html.md +31 -0
  20. data/docsite/source/custom-types.html.md +93 -0
  21. data/docsite/source/default-values.html.md +91 -0
  22. data/docsite/source/enum.html.md +69 -0
  23. data/docsite/source/extensions/maybe.html.md +57 -0
  24. data/docsite/source/extensions/monads.html.md +61 -0
  25. data/docsite/source/extensions.html.md +15 -0
  26. data/docsite/source/getting-started.html.md +57 -0
  27. data/docsite/source/hash-schemas.html.md +169 -0
  28. data/docsite/source/index.html.md +156 -0
  29. data/docsite/source/map.html.md +17 -0
  30. data/docsite/source/optional-values.html.md +35 -0
  31. data/docsite/source/sum.html.md +21 -0
  32. data/lib/dry/types/any.rb +2 -2
  33. data/lib/dry/types/array/member.rb +3 -3
  34. data/lib/dry/types/builder.rb +1 -1
  35. data/lib/dry/types/builder_methods.rb +18 -8
  36. data/lib/dry/types/compiler.rb +2 -2
  37. data/lib/dry/types/constrained.rb +1 -1
  38. data/lib/dry/types/constructor.rb +6 -33
  39. data/lib/dry/types/decorator.rb +3 -2
  40. data/lib/dry/types/enum.rb +1 -1
  41. data/lib/dry/types/extensions/monads.rb +29 -0
  42. data/lib/dry/types/extensions.rb +4 -0
  43. data/lib/dry/types/hash.rb +3 -2
  44. data/lib/dry/types/lax.rb +1 -1
  45. data/lib/dry/types/meta.rb +1 -1
  46. data/lib/dry/types/module.rb +3 -3
  47. data/lib/dry/types/params.rb +5 -0
  48. data/lib/dry/types/predicate_inferrer.rb +197 -0
  49. data/lib/dry/types/predicate_registry.rb +34 -0
  50. data/lib/dry/types/primitive_inferrer.rb +97 -0
  51. data/lib/dry/types/spec/types.rb +3 -1
  52. data/lib/dry/types/sum.rb +2 -2
  53. data/lib/dry/types/version.rb +1 -1
  54. data/lib/dry/types.rb +7 -2
  55. metadata +29 -4
  56. data/.travis.yml +0 -27
@@ -0,0 +1,156 @@
1
+ ---
2
+ title: Introduction
3
+ layout: gem-single
4
+ type: gem
5
+ name: dry-types
6
+ sections:
7
+ - getting-started
8
+ - built-in-types
9
+ - optional-values
10
+ - default-values
11
+ - sum
12
+ - constraints
13
+ - hash-schemas
14
+ - array-with-member
15
+ - enum
16
+ - map
17
+ - custom-types
18
+ - extensions
19
+ ---
20
+
21
+ `dry-types` is a simple and extendable type system for Ruby; useful for value coercions, applying constraints, defining complex structs or value objects and more. It was created as a successor to [Virtus](https://github.com/solnic/virtus).
22
+
23
+ ### Example usage
24
+
25
+ ```ruby
26
+ require 'dry-types'
27
+ require 'dry-struct'
28
+
29
+ module Types
30
+ include Dry.Types()
31
+ end
32
+
33
+ User = Dry.Struct(name: Types::String, age: Types::Integer)
34
+
35
+ User.new(name: 'Bob', age: 35)
36
+ # => #<User name="Bob" age=35>
37
+ ```
38
+
39
+ See [Built-in Types](docs::built-in-types/) for a full list of available types.
40
+
41
+ By themselves, the basic type definitions like `Types::String` and `Types::Integer` don't do anything except provide documentation about which type an attribute is expected to have. However, there are many more advanced possibilities:
42
+
43
+ - `Strict` types will raise an error if passed an attribute of the wrong type:
44
+
45
+ ```ruby
46
+ class User < Dry::Struct
47
+ attribute :name, Types::Strict::String
48
+ attribute :age, Types::Strict::Integer
49
+ end
50
+
51
+ User.new(name: 'Bob', age: '18')
52
+ # => Dry::Struct::Error: [User.new] "18" (String) has invalid type for :age
53
+ ```
54
+
55
+ - `Coercible` types will attempt to convert an attribute to the correct class
56
+ using Ruby's built-in coercion methods:
57
+
58
+ ```ruby
59
+ class User < Dry::Struct
60
+ attribute :name, Types::Coercible::String
61
+ attribute :age, Types::Coercible::Integer
62
+ end
63
+
64
+ User.new(name: 'Bob', age: '18')
65
+ # => #<User name="Bob" age=18>
66
+ User.new(name: 'Bob', age: 'not coercible')
67
+ # => ArgumentError: invalid value for Integer(): "not coercible"
68
+ ```
69
+
70
+ - Use `.optional` to denote that an attribute can be `nil` (see [Optional Values](docs::optional-values)):
71
+
72
+ ```ruby
73
+ class User < Dry::Struct
74
+ attribute :name, Types::String
75
+ attribute :age, Types::Integer.optional
76
+ end
77
+
78
+ User.new(name: 'Bob', age: nil)
79
+ # => #<User name="Bob" age=nil>
80
+ # name is not optional:
81
+ User.new(name: nil, age: 18)
82
+ # => Dry::Struct::Error: [User.new] nil (NilClass) has invalid type for :name
83
+ # keys must still be present:
84
+ User.new(name: 'Bob')
85
+ # => Dry::Struct::Error: [User.new] :age is missing in Hash input
86
+ ```
87
+
88
+ - Add custom constraints (see [Constraints](docs::constraints.html)):
89
+
90
+ ```ruby
91
+ class User < Dry::Struct
92
+ attribute :name, Types::Strict::String
93
+ attribute :age, Types::Strict::Integer.constrained(gteq: 18)
94
+ end
95
+
96
+ User.new(name: 'Bob', age: 17)
97
+ # => Dry::Struct::Error: [User.new] 17 (Fixnum) has invalid type for :age
98
+ ```
99
+
100
+ - Add custom metadata to a type:
101
+
102
+ ```ruby
103
+ class User < Dry::Struct
104
+ attribute :name, Types::String
105
+ attribute :age, Types::Integer.meta(info: 'extra info about age')
106
+ end
107
+ ```
108
+
109
+ - Pass values directly to `Dry::Types` without creating an object using `[]`:
110
+
111
+ ```ruby
112
+ Types::Strict::String["foo"]
113
+ # => "foo"
114
+ Types::Strict::String["10000"]
115
+ # => "10000"
116
+ Types::Coercible::String[10000]
117
+ # => "10000"
118
+ Types::Strict::String[10000]
119
+ # Dry::Types::ConstraintError: 1000 violates constraints
120
+ ```
121
+
122
+ ### Features
123
+
124
+ * Support for [constrained types](docs::constraints)
125
+ * Support for [optional values](docs::optional-values)
126
+ * Support for [default values](docs::default-values)
127
+ * Support for [sum types](docs::sum)
128
+ * Support for [enums](docs::enum)
129
+ * Support for [hash type with type schemas](docs::hash-schemas)
130
+ * Support for [array type with members](docs::array-with-member)
131
+ * Support for arbitrary meta information
132
+ * Support for typed struct objects via [dry-struct](/gems/dry-struct)
133
+ * Types are [categorized](docs::built-in-types), which is especially important for optimized and dedicated coercion logic
134
+ * Types are composable and reusable objects
135
+ * No const-missing magic and complicated const lookups
136
+ * Roughly 6-10 x faster than Virtus
137
+
138
+ ### Use cases
139
+
140
+ `dry-types` is suitable for many use-cases, for example:
141
+
142
+ * Value coercions
143
+ * Processing arrays
144
+ * Processing hashes with explicit schemas
145
+ * Defining various domain-specific information shared between multiple parts of your application
146
+ * Annotating objects
147
+
148
+ ### Other gems using dry-types
149
+
150
+ `dry-types` is often used as a low-level abstraction. The following gems use it already:
151
+
152
+ * [dry-struct](/gems/dry-struct)
153
+ * [dry-initializer](/gems/dry-initializer)
154
+ * [Hanami](http://hanamirb.org)
155
+ * [rom-rb](http://rom-rb.org)
156
+ * [Trailblazer](http://trailblazer.to)
@@ -0,0 +1,17 @@
1
+ ---
2
+ title: Map
3
+ layout: gem-single
4
+ name: dry-types
5
+ ---
6
+
7
+ `Map` describes a homogeneous hashmap. This means only types of keys and values are known. You can simply imagine a map input as a list of key-value pairs.
8
+
9
+ ```ruby
10
+ int_float_hash = Types::Hash.map(Types::Integer, Types::Float)
11
+ int_float_hash[100 => 300.0, 42 => 70.0]
12
+ # => {100=>300.0, 42=>70.0}
13
+
14
+ # Only accepts mappings of integers to floats
15
+ int_float_hash[name: 'Jane']
16
+ # => Dry::Types::MapError: input key :name is invalid: type?(Integer, :name)
17
+ ```
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: Type Attributes
3
+ layout: gem-single
4
+ name: dry-types
5
+ ---
6
+
7
+ Types themselves have optional attributes you can apply to get further functionality.
8
+
9
+ ### Append `.optional` to a _Type_ to allow `nil`
10
+
11
+ By default, nil values raise an error:
12
+
13
+ ``` ruby
14
+ Types::Strict::String[nil]
15
+ # => raises Dry::Types::ConstraintError
16
+ ```
17
+
18
+ Add `.optional` and `nil` values become valid:
19
+
20
+ ```ruby
21
+ optional_string = Types::Strict::String.optional
22
+
23
+ optional_string[nil]
24
+ # => nil
25
+ optional_string['something']
26
+ # => "something"
27
+ optional_string[123]
28
+ # raises Dry::Types::ConstraintError
29
+ ```
30
+
31
+ `Types::String.optional` is just syntactic sugar for `Types::Strict::Nil | Types::Strict::String`.
32
+
33
+ ### Handle optional values using Monads
34
+
35
+ See [Maybe](docs::extensions/maybe) extension for another approach to handling optional values by returning a [_Monad_](/gems/dry-monads/) object.
@@ -0,0 +1,21 @@
1
+ ---
2
+ title: Sum
3
+ layout: gem-single
4
+ name: dry-types
5
+ order: 7
6
+ ---
7
+
8
+ You can specify sum types using `|` operator, it is an explicit way of defining what the valid types of a value are.
9
+
10
+ For example `dry-types` defines the `Bool` type which is a sum consisting of the `True` and `False` types, expressed as `Types::True | Types::False`.
11
+
12
+ Another common case is defining that something can be either `nil` or something else:
13
+
14
+ ``` ruby
15
+ nil_or_string = Types::Nil | Types::String
16
+
17
+ nil_or_string[nil] # => nil
18
+ nil_or_string["hello"] # => "hello"
19
+
20
+ nil_or_string[123] # raises Dry::Types::ConstraintError
21
+ ```
data/lib/dry/types/any.rb CHANGED
@@ -15,7 +15,7 @@ module Dry
15
15
 
16
16
  # @api private
17
17
  def initialize(**options)
18
- super(::Object, options)
18
+ super(::Object, **options)
19
19
  end
20
20
 
21
21
  # @return [String]
@@ -30,7 +30,7 @@ module Dry
30
30
  # @return [Type]
31
31
  #
32
32
  # @api public
33
- def with(new_options)
33
+ def with(**new_options)
34
34
  self.class.new(**options, meta: @meta, **new_options)
35
35
  end
36
36
 
@@ -18,7 +18,7 @@ module Dry
18
18
  # @option options [Type] :member
19
19
  #
20
20
  # @api private
21
- def initialize(primitive, options = {})
21
+ def initialize(primitive, **options)
22
22
  @member = options.fetch(:member)
23
23
  super
24
24
  end
@@ -89,7 +89,7 @@ module Dry
89
89
  block ? yield(failure) : failure
90
90
  end
91
91
  else
92
- failure = failure(input, "#{input} is not an array")
92
+ failure = failure(input, CoercionError.new("#{input} is not an array"))
93
93
  block ? yield(failure) : failure
94
94
  end
95
95
  end
@@ -100,7 +100,7 @@ module Dry
100
100
  #
101
101
  # @api public
102
102
  def lax
103
- Lax.new(Member.new(primitive, { **options, member: member.lax, meta: meta }))
103
+ Lax.new(Member.new(primitive, **options, member: member.lax, meta: meta))
104
104
  end
105
105
 
106
106
  # @see Nominal#to_ast
@@ -127,7 +127,7 @@ module Dry
127
127
  #
128
128
  # @api public
129
129
  def constructor(constructor = nil, **options, &block)
130
- constructor_type.new(with(options), fn: constructor || block)
130
+ constructor_type.new(with(**options), fn: constructor || block)
131
131
  end
132
132
  end
133
133
  end
@@ -25,7 +25,7 @@ module Dry
25
25
  #
26
26
  # @return [Dry::Types::Array]
27
27
  def Array(type)
28
- self::Array.of(type)
28
+ Strict(::Array).of(type)
29
29
  end
30
30
 
31
31
  # Build a hash schema
@@ -34,7 +34,7 @@ module Dry
34
34
  #
35
35
  # @return [Dry::Types::Array]
36
36
  def Hash(type_map)
37
- self::Hash.schema(type_map)
37
+ Strict(::Hash).schema(type_map)
38
38
  end
39
39
 
40
40
  # Build a type which values are instances of a given class
@@ -49,7 +49,7 @@ module Dry
49
49
  #
50
50
  # @return [Dry::Types::Type]
51
51
  def Instance(klass)
52
- Nominal.new(klass).constrained(type: klass)
52
+ Nominal(klass).constrained(type: klass)
53
53
  end
54
54
  alias_method :Strict, :Instance
55
55
 
@@ -60,7 +60,7 @@ module Dry
60
60
  #
61
61
  # @return [Dry::Types::Type]
62
62
  def Value(value)
63
- Nominal.new(value.class).constrained(eql: value)
63
+ Nominal(value.class).constrained(eql: value)
64
64
  end
65
65
 
66
66
  # Build a type with a single value
@@ -70,7 +70,7 @@ module Dry
70
70
  #
71
71
  # @return [Dry::Types::Type]
72
72
  def Constant(object)
73
- Nominal.new(object.class).constrained(is: object)
73
+ Nominal(object.class).constrained(is: object)
74
74
  end
75
75
 
76
76
  # Build a constructor type
@@ -82,7 +82,11 @@ module Dry
82
82
  #
83
83
  # @return [Dry::Types::Type]
84
84
  def Constructor(klass, cons = nil, &block)
85
- Nominal.new(klass).constructor(cons || block || klass.method(:new))
85
+ if klass.is_a?(Type)
86
+ klass.constructor(cons || block || klass.method(:new))
87
+ else
88
+ Nominal(klass).constructor(cons || block || klass.method(:new))
89
+ end
86
90
  end
87
91
 
88
92
  # Build a nominal type
@@ -91,7 +95,13 @@ module Dry
91
95
  #
92
96
  # @return [Dry::Types::Type]
93
97
  def Nominal(klass)
94
- Nominal.new(klass)
98
+ if klass <= ::Array
99
+ Array.new(klass)
100
+ elsif klass <= ::Hash
101
+ Hash.new(klass)
102
+ else
103
+ Nominal.new(klass)
104
+ end
95
105
  end
96
106
 
97
107
  # Build a map type
@@ -105,7 +115,7 @@ module Dry
105
115
  #
106
116
  # @return [Dry::Types::Map]
107
117
  def Map(key_type, value_type)
108
- Types['nominal.hash'].map(key_type, value_type)
118
+ Nominal(::Hash).map(key_type, value_type)
109
119
  end
110
120
 
111
121
  # Builds a constrained nominal type accepting any value that
@@ -68,12 +68,12 @@ module Dry
68
68
 
69
69
  def visit_hash(node)
70
70
  opts, meta = node
71
- registry['nominal.hash'].with(opts.merge(meta: meta))
71
+ registry['nominal.hash'].with(**opts, meta: meta)
72
72
  end
73
73
 
74
74
  def visit_schema(node)
75
75
  keys, options, meta = node
76
- registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(options.merge(meta: meta))
76
+ registry['nominal.hash'].schema(keys.map { |key| visit(key) }).with(**options, meta: meta)
77
77
  end
78
78
 
79
79
  def visit_json_hash(node)
@@ -24,7 +24,7 @@ module Dry
24
24
  # @param [Hash] options
25
25
  #
26
26
  # @api public
27
- def initialize(type, options)
27
+ def initialize(type, **options)
28
28
  super
29
29
  @rule = options.fetch(:rule)
30
30
  end
@@ -12,15 +12,13 @@ module Dry
12
12
  class Constructor < Nominal
13
13
  include Dry::Equalizer(:type, :options, inspect: false)
14
14
 
15
- private :meta
16
-
17
15
  # @return [#call]
18
16
  attr_reader :fn
19
17
 
20
18
  # @return [Type]
21
19
  attr_reader :type
22
20
 
23
- undef :constrained?
21
+ undef :constrained?, :meta, :optional?, :primitive, :default?, :name
24
22
 
25
23
  # @param [Builder, Object] input
26
24
  # @param [Hash] options
@@ -46,31 +44,6 @@ module Dry
46
44
  super(type, **options, fn: fn)
47
45
  end
48
46
 
49
- # Return the inner type's primitive
50
- #
51
- # @return [Class]
52
- #
53
- # @api public
54
- def primitive
55
- type.primitive
56
- end
57
-
58
- # Return the inner type's name
59
- #
60
- # @return [String]
61
- #
62
- # @api public
63
- def name
64
- type.name
65
- end
66
-
67
- # @return [Boolean]
68
- #
69
- # @api public
70
- def default?
71
- type.default?
72
- end
73
-
74
47
  # @return [Object]
75
48
  #
76
49
  # @api private
@@ -112,7 +85,7 @@ module Dry
112
85
  #
113
86
  # @api public
114
87
  def constructor(new_fn = nil, **options, &block)
115
- with({**options, fn: fn >> (new_fn || block)})
88
+ with(**options, fn: fn >> (new_fn || block))
116
89
  end
117
90
  alias_method :append, :constructor
118
91
  alias_method :>>, :constructor
@@ -141,7 +114,7 @@ module Dry
141
114
  #
142
115
  # @api public
143
116
  def prepend(new_fn = nil, **options, &block)
144
- with({**options, fn: fn << (new_fn || block)})
117
+ with(**options, fn: fn << (new_fn || block))
145
118
  end
146
119
  alias_method :<<, :prepend
147
120
 
@@ -150,7 +123,7 @@ module Dry
150
123
  # @return [Lax]
151
124
  # @api public
152
125
  def lax
153
- Lax.new(Constructor.new(type.lax, options))
126
+ Lax.new(Constructor.new(type.lax, **options))
154
127
  end
155
128
 
156
129
  # Wrap the type with a proc
@@ -182,10 +155,10 @@ module Dry
182
155
  # @api private
183
156
  def method_missing(method, *args, &block)
184
157
  if type.respond_to?(method)
185
- response = type.__send__(method, *args, &block)
158
+ response = type.public_send(method, *args, &block)
186
159
 
187
160
  if response.is_a?(Type) && type.class == response.class
188
- response.constructor_type.new(response, options)
161
+ response.constructor_type.new(response, **options)
189
162
  else
190
163
  response
191
164
  end
@@ -14,7 +14,7 @@ module Dry
14
14
  attr_reader :type
15
15
 
16
16
  # @param [Type] type
17
- def initialize(type, *)
17
+ def initialize(type, *, **)
18
18
  super
19
19
  @type = type
20
20
  end
@@ -90,7 +90,7 @@ module Dry
90
90
  # @api private
91
91
  def method_missing(meth, *args, &block)
92
92
  if type.respond_to?(meth)
93
- response = type.__send__(meth, *args, &block)
93
+ response = type.public_send(meth, *args, &block)
94
94
 
95
95
  if decorate?(response)
96
96
  __new__(response)
@@ -101,6 +101,7 @@ module Dry
101
101
  super
102
102
  end
103
103
  end
104
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
104
105
 
105
106
  # Replace underlying type
106
107
  #
@@ -27,7 +27,7 @@ module Dry
27
27
  # @option options [Array] :values
28
28
  #
29
29
  # @api private
30
- def initialize(type, options)
30
+ def initialize(type, **options)
31
31
  super
32
32
  @mapping = options.fetch(:mapping).freeze
33
33
  @values = @mapping.keys.freeze
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/monads/result'
4
+
5
+ module Dry
6
+ module Types
7
+ # Monad extension for Result
8
+ #
9
+ # @api public
10
+ class Result
11
+ include Dry::Monads::Result::Mixin
12
+
13
+ # Turn result into a monad
14
+ #
15
+ # This makes result objects work with dry-monads (or anything with a compatible interface)
16
+ #
17
+ # @return [Dry::Monads::Success,Dry::Monads::Failure]
18
+ #
19
+ # @api public
20
+ def to_monad
21
+ if success?
22
+ Success(input)
23
+ else
24
+ Failure([error, input])
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -3,3 +3,7 @@
3
3
  Dry::Types.register_extension(:maybe) do
4
4
  require 'dry/types/extensions/maybe'
5
5
  end
6
+
7
+ Dry::Types.register_extension(:monads) do
8
+ require 'dry/types/extensions/monads'
9
+ end
@@ -113,7 +113,7 @@ module Dry
113
113
 
114
114
  type_map.map do |map_key, type|
115
115
  name, options = key_name(map_key)
116
- key = Schema::Key.new(resolve_type(type), name, options)
116
+ key = Schema::Key.new(resolve_type(type), name, **options)
117
117
  type_transform.(key)
118
118
  end
119
119
  end
@@ -121,7 +121,8 @@ module Dry
121
121
  # @api private
122
122
  def resolve_type(type)
123
123
  case type
124
- when String, Class then Types[type]
124
+ when Type then type
125
+ when ::Class, ::String then Types[type]
125
126
  else type
126
127
  end
127
128
  end
data/lib/dry/types/lax.rb CHANGED
@@ -15,7 +15,7 @@ module Dry
15
15
  include Printable
16
16
  include Dry::Equalizer(:type, inspect: false)
17
17
 
18
- private :options, :constructor
18
+ undef :options, :constructor
19
19
 
20
20
  # @param [Object] input
21
21
  #
@@ -16,7 +16,7 @@ module Dry
16
16
  # @return [Type]
17
17
  #
18
18
  # @api public
19
- def with(options)
19
+ def with(**options)
20
20
  super(meta: @meta, **options)
21
21
  end
22
22
 
@@ -18,10 +18,10 @@ module Dry
18
18
  #
19
19
  # @api public
20
20
  class Module < ::Module
21
- def initialize(registry, *args)
21
+ def initialize(registry, *args, **kwargs)
22
22
  @registry = registry
23
- check_parameters(*args)
24
- constants = type_constants(*args)
23
+ check_parameters(*args, **kwargs)
24
+ constants = type_constants(*args, **kwargs)
25
25
  define_constants(constants)
26
26
  extend(BuilderMethods)
27
27
 
@@ -55,5 +55,10 @@ module Dry
55
55
  register('params.symbol') do
56
56
  self['nominal.symbol'].constructor(Coercions::Params.method(:to_symbol))
57
57
  end
58
+
59
+ COERCIBLE.each_key do |name|
60
+ next if name.equal?(:string)
61
+ register("optional.params.#{name}", self['params.nil'] | self["params.#{name}"])
62
+ end
58
63
  end
59
64
  end